/**
* PassportBox API Sample File
* Copyright (c) 2012-2016, Smart Engines. All rights reserved.
*/
#ifndef _CRT_SECURE_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
#endif  // _CRT_SECURE_NO_WARNINGS

#include <passport_box_api/passport_box_api.h>

#include <stdexcept>
#include <iostream>
#include <cstring>
#include <string>
#include <thread>

#ifdef WIN32
#include <windows.h>
#include <conio.h>
#endif // WIN32

static std::string utf8ToConsole(const std::string &utf8)
{
#ifdef WIN32

  int wide_size = MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), utf8.size(), 0, 0) + 1;
  wchar_t *wide = new wchar_t[wide_size]; memset(wide, 0, sizeof(wchar_t) * wide_size);
  MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), utf8.size(), wide, wide_size);

  int local_size = WideCharToMultiByte(CP_ACP, 0, wide, wide_size, 0, 0, 0, 0) + 1;
  char *local = new char[local_size]; memset(local, 0, sizeof(char) * local_size);
  WideCharToMultiByte(CP_ACP, 0, wide, wide_size, local, local_size, 0, 0);

  std::string cyrillic = local;

  delete[] local;
  delete[] wide;

#else  // WIN32

  std::string cyrillic = utf8;

#endif // WIN32

  return cyrillic;
}

static void ConvertTo25Symbols(std::string& str) {
  while (str.size() < 25) {
    str.push_back(' ');
  }
}

static void printTextRecogResult()
{
  try 
  {
    PBAPI_DocInfo docInfo = {0};
    PBAPI_GetDocumentInfo(&docInfo);
    std::cout << "Document: " << docInfo.doc_type << std::endl;
    std::cout << "Field count: " << docInfo.field_count << std::endl;
    std::cout << "Image count: " << docInfo.image_count << std::endl;

    for (int i = 0; i < docInfo.image_count; i++)
    {
      int image_size = 0;
      char image_name[4096] = { 0 };
      PBAPI_GetImageName(i, image_name, sizeof(image_name));
      PBAPI_GetImageBase64Size(i, &image_size);
      std::cout << "Image " << i << ", name: " << image_name << ", size (in bytes, base64): " << image_size << std::endl;
    }

    PBAPI_FieldInfo fieldInfo = {0};
    std::string tmpFieldName, tmpFieldValue, tmpFieldAccepted, tmpCoordinates, tmpRawCoordinates;;
    for (int i = 0; i < docInfo.field_count; ++i) 
    {
      PBAPI_GetFieldInfo(i, &fieldInfo);
      tmpFieldAccepted = fieldInfo.is_accepted ? " [+] " : " [-] ";
      tmpFieldName = fieldInfo.field_name;
      tmpFieldName += ':';
      ConvertTo25Symbols(tmpFieldName);
      tmpFieldValue = fieldInfo.field_value;
      tmpCoordinates = fieldInfo.field_coordinates;
      tmpRawCoordinates = fieldInfo.field_raw_coordinates;
      std::cout << tmpFieldName << tmpFieldAccepted << utf8ToConsole(tmpFieldValue) << std::endl;
      if (tmpRawCoordinates != "[]") {
        std::cout << "  raw:" << tmpRawCoordinates << std::endl;
      }
      if (tmpCoordinates != "[]") {
        std::cout << "  crop:" << tmpCoordinates << std::endl;
      }
      
    }
  }
  catch(std::exception &ex)
  {
    std::cerr << ex.what() << std::endl;
  }
}

static void documentCaptureCB(int ret_code, void *cb_data)
{
  if (ret_code == PBAPI_CB_DOCUMENT_DETECTED)
  {
    std::cout << std::endl << "Document was detected" << std::endl;
  }
  else if (ret_code == PBAPI_CB_DOCUMENT_RECOGNIZED)
  {
    std::cout << std::endl;
    printTextRecogResult();
  }
  else if (ret_code == PBAPI_CB_DOCUMENT_REMOVED)
  {
    std::cout << std::endl << "Document was removed" << std::endl;
  }
  else if (ret_code == PBAPI_CB_ERROR)
  {
    PBAPI_ErrorInfo error_info = { 0 }; PBAPI_GetLastError(&error_info);
    std::cerr << std::endl << error_info.err_code << ", " << error_info.err_message << std::endl;
  }
}

int main(int argc, char *argv[])
{
#if WIN32  
  setlocale(LC_CTYPE, "Russian");
#endif
  try
  {
    int snapshot_index = 0;
    const std::string config_path = "passport_box_api.json";
    if (PBAPI_Configure(config_path.c_str()) < 0)
      throw std::runtime_error("Something went wrong with configuration");

    if (PBAPI_OpenCaptureDevice() < 0)
      throw std::runtime_error("Couldn't open capture device");
    
    std::cout << "Smart PassportBox API Sample application" << std::endl;
    std::cout << "\tPress '0' to specify recognized documents" << std::endl;
    std::cout << "\tPress '1' to recognize passport in synchronous mode" << std::endl;
    std::cout << "\tPress '2' to recognize passport in asynchronous mode" << std::endl;
    std::cout << "\tPress '3' to start/stop auto recognition" << std::endl;
    std::cout << "\tPress '4' to recognize current frame" << std::endl;
    std::cout << "\tPress 'f' to check camera focus (100 - is the best result)" << std::endl;
    std::cout << "\tPress 'c' to calibrate" << std::endl;
    std::cout << "\tPress 'd' to dump configure(if empty - current config filepath will be used)" << std::endl;
    std::cout << "\tPress 'e' to show current enabled doctypes" << std::endl;
    std::cout << "\tPress 's' to take snapshot" << std::endl;
    std::cout << "\tPress 'i' to get current document info" << std::endl;
    std::cout << "\tPress 'q' to exit" << std::endl << std::endl;

    bool is_auto_capture_enabled = false;
    bool should_exit = false;

    PBAPI_CaptureSettings capture_settings = PBAPI_DEFAULT_CAPTURESETTINGS;
    PBAPI_AutoCaptureSettings auto_settings = PBAPI_DEFAULT_AUTOCAPTURESETTINGS;

    while (should_exit == false)
    {
      std::cout << "Press {0|1|2|3|4|f|c|d|e|s|i|q}: ";
#if WIN32
      int c = _getch();
      std::cout << (char)c << std::endl;
#else  // WIN32
      int c = getchar();
#endif // WIN32 

      try
      {
        switch (c)
        {
        case '0':
        {
          char supported_doc_classes_buf[4096] = { 0 };
          PBAPI_GetDocumentClasses(supported_doc_classes_buf, sizeof(supported_doc_classes_buf));

          std::cout << "Enter a set of documents to be recognized (empty string - all documents): ";
          std::string doc_classes; 
          std::getline(std::cin, doc_classes);

          memset(capture_settings.doc_classes, 0, sizeof(capture_settings.doc_classes));
          strcpy(capture_settings.doc_classes, doc_classes.c_str());
          break;
        }
        case '1':
        {
          if (is_auto_capture_enabled)
          {
            if (PBAPI_StopAutoCaptureDocument() < 0)
              throw std::exception();

            is_auto_capture_enabled = false;
            std::cout << "Auto capture mode was disabled" << std::endl;
          }
          if (PBAPI_CaptureDocument(&capture_settings) < 0)
            throw std::exception();

          printTextRecogResult();
          break;
        }

        case '2':
        {
          if (is_auto_capture_enabled)
          {
            if (PBAPI_StopAutoCaptureDocument() < 0)
              throw std::exception();

            is_auto_capture_enabled = false;
            std::cout << "Auto capture mode was disabled" << std::endl;
          }
          if (PBAPI_CaptureDocumentAsync(documentCaptureCB, 0, &capture_settings) < 0)
            throw std::exception();

          std::cout << "When the passport is recognized the callback function will print results" << std::endl;
          break;
        }

        case '3':
        {
          if (is_auto_capture_enabled)
          {
            if (PBAPI_StopAutoCaptureDocument() < 0)
              throw std::exception();

            is_auto_capture_enabled = false;
            std::cout << "Auto capture mode was disabled" << std::endl;
          }
          else
          {
            if (PBAPI_StartAutoCaptureDocument(documentCaptureCB, 0, &auto_settings, &capture_settings) < 0)
              throw std::exception();

            is_auto_capture_enabled = true;
            std::cout << "Auto capture mode was enabled. Put the document on workspace and wait..." << std::endl;
          }
          break;
        }
        case '4':
        {

          if (PBAPI_RecognizeCurrentFrame(true, &capture_settings) < 0)
            throw std::exception();

          printTextRecogResult();
          break;
        }
        case 'f':
        {
          int focus_ratio = 0;
          if (PBAPI_FocusRatio(&focus_ratio) < 0)
            throw std::runtime_error("Problem in PBAPI_FocusRatio");
          std::cout << "Focus_ratio: " << focus_ratio << '%' <<  std::endl;
          break;
        }
        case 'c':
        {
          if (PBAPI_Calibrate() < 0)
            throw std::runtime_error("Problem in PBAPI_Calibrate");
          std::cout << "Calibration done" << std::endl;
          break;
        }
        case 'd':
        {
          std::string new_path;
          std::cout << "Print new config filename (empty string - save to source config)" << std::endl;
          std::getline(std::cin, new_path);
          if (new_path.empty())
            new_path = config_path;

          if (PBAPI_DumpConfigure(new_path.c_str()) < 0)
            throw std::runtime_error("Problem in PBAPI_DumpConfigure");
          std::cout << "Configuration serialized to " << new_path << std::endl; 
          break;
        }
        case 'e':
        {
          std::string mask;
          std::cout << "Current Enabled Doctypes" << std::endl;
          int size;
          PBAPI_GetCurrentDocsBufSize(&size);
          char* doc_buf = new char[size];
          PBAPI_GetCurrentDocs(doc_buf, size);
          std::cout << doc_buf << std::endl;
          break;
        }
        case 's':
        {
          char snapshot_name[1024] = { 0 };
          sprintf(snapshot_name, "%06d.jpg", snapshot_index++);
          if (PBAPI_TakeSnapshot() < 0)
            throw std::exception();
          if (PBAPI_SaveSnapshot(snapshot_name) < 0)
            throw std::exception();

          std::cout << "Snapshot saved to " << snapshot_name << std::endl;
          break;
        }
        case 'i': {
          printTextRecogResult();
          break;
        }
        case 'q':
        {
          should_exit = true;
          break;
        }
        }
      }
      catch (...)
      {
        PBAPI_ErrorInfo error_info = { 0 }; PBAPI_GetLastError(&error_info);
        std::cerr << error_info.err_code << ", " << error_info.err_message << std::endl;
      }
    }
  }
  catch (...)
  {
    PBAPI_ErrorInfo error_info = { 0 }; PBAPI_GetLastError(&error_info);
    std::cerr << error_info.err_code << ", " << error_info.err_message << std::endl;
  }

  if (PBAPI_StopAutoCaptureDocument() < 0)
  {
    PBAPI_ErrorInfo error_info = { 0 }; PBAPI_GetLastError(&error_info);
    std::cerr << error_info.err_code << ", " << error_info.err_message << std::endl;
  }

  if (PBAPI_CloseCaptureDevice() < 0)
  {
    PBAPI_ErrorInfo error_info = { 0 }; PBAPI_GetLastError(&error_info);
    std::cerr << error_info.err_code << ", " << error_info.err_message << std::endl;
  }

  return 0;
}
