#include <assert.h>
#include <iostream>
#include <vector>

#include "..\..\..\..\deprtmnt.h"
#include "..\..\..\..\errors.h"
#include "..\..\..\..\gate.h"
#include "..\..\..\..\sbkernel.h"

#ifndef WINCE
  const char* CALL_SB_KERNEL_NAME = "_call_sb_kernel";
#else
  const char* CALL_SB_KERNEL_NAME = "call_sb_kernel";
#endif

  struct Error
  {
    DWORD Line;
    DWORD Code;
    std::string Text;

    Error(DWORD line, DWORD code, const char* text)
      : Line(line), Code(code), Text(text)
    {}
  };

#define throw_if_error(code, text) if (code) throw Error(__LINE__, code, text)

void initialize(Gate_Call_Sb_Kernel function)
{
  InArg in;
  OutArg out;
  struct_in0 InDetail;
  struct_out0 OutDetail;
  memset(&in, 0, sizeof(in));
  memset(&InDetail, 0, sizeof(InDetail));
  in.in_struct = &InDetail;
  InDetail.AppVersion = KERNEL_MINIMAL_VERSION;
  memset(&out, 0, sizeof(out));
  memset(&OutDetail, 0, sizeof(OutDetail));
  out.out_struct = &OutDetail;

  DWORD error = function(KO_INITIALIZATION, &in, &out);
  throw_if_error(error, "initialization");
  throw_if_error(out.ErrorCode, "initialization");
  std::cout << ((OutDetail.LibVersion/65536)&0xFF) << "." <<((OutDetail.LibVersion/256)&0xFF) << "." << (OutDetail.LibVersion&0xFF) << std::endl;
}

bool de_initialize(Gate_Call_Sb_Kernel function)
{
  InArg in;
  OutArg out;
  memset(&in,0,sizeof(in));
  memset(&out,0,sizeof(out));

  if(function && !function(1, &in, &out))
      return true;

  return false;
}

DWORD get_functions_count(Gate_Call_Sb_Kernel function)
{
  InArg in;
  OutArg out;
  struct_out2 out2;

  memset(&in,0,sizeof(in));

  memset(&out,0,sizeof(out));
  memset(&out2,0,sizeof(out2));
  out.out_struct = &out2;

  DWORD error = function(2,&in,&out);
  throw_if_error(error, "get_functions_count");
  throw_if_error(out.ErrorCode, "get_functions_count");

  return out2.Count;
}

void get_functions_list(Gate_Call_Sb_Kernel function, EnumStruct* functions)
{
  InArg in;
  OutArg out;
  struct_out3 out3;

  memset(&in,0,sizeof(in));

  memset(&out,0,sizeof(out));
  memset(&out3,0,sizeof(out3));
  out.out_struct = &out3;
  out3.Buffer = functions;

  DWORD error = function(3,&in,&out);
  throw_if_error(error, "get_functions_list");
  throw_if_error(out.ErrorCode, "get_functions_list");
}

DWORD get_cheque_size(Gate_Call_Sb_Kernel function)
{
  InArg in;
  OutArg out;
  struct_out4 out4;

  memset(&in,0,sizeof(in));

  memset(&out,0,sizeof(out));
  memset(&out4,0,sizeof(out4));
  out.out_struct = &out4;

  DWORD error = function(4,&in,&out);
  throw_if_error(error, "get_cheque_size");
  if (out.ErrorCode == ERR_NO_DOCUMENT)
  {
    std::cout << "Last cheque was not found. Please perform operation with cheque before.";
    return 0;
  }

  throw_if_error(out.ErrorCode, "get_cheque_size");
  return out4.Size;
}

void get_last_cheque(Gate_Call_Sb_Kernel function, char* cheque)
{
  InArg in;
  OutArg out;
  struct_out5 out5;

  memset(&in,0,sizeof(in));

  memset(&out,0,sizeof(out));
  memset(&out5,0,sizeof(out5));
  out.out_struct = &out5;
  out5.Buffer = cheque;

  DWORD error = function(5,&in,&out);
  throw_if_error(error, "get_last_cheque");
  throw_if_error(out.ErrorCode, "get_last_cheque");
}

/* Always 1 for PCI DSS */
DWORD get_currency_count(Gate_Call_Sb_Kernel function)
{
  InArg in;
  OutArg out;
  struct_out6 out6;

  memset(&in,0,sizeof(in));

  memset(&out,0,sizeof(out));
  memset(&out6,0,sizeof(out6));
  out.out_struct = &out6;

  DWORD error = function(6,&in,&out);
  throw_if_error(error, "get_currency_count");
  throw_if_error(out.ErrorCode, "get_currency_count");
  return out6.Count;
}

void get_currencies(Gate_Call_Sb_Kernel function, CurrDesc* currencies)
{
  InArg in;
  OutArg out;
  struct_out7 out7;

  memset(&in,0,sizeof(in));
  memset(&out,0,sizeof(out));
  memset(&out7,0,sizeof(out7));

  out.out_struct = &out7;
  out7.Buffer = currencies;

  DWORD error = function(7,&in,&out);
  throw_if_error(error, "get_currencies");
  throw_if_error(out.ErrorCode, "get_currencies");
}

void set_handles_for_os_message_queue(Gate_Call_Sb_Kernel function, int* textMessageHandle, int* editorMessageHandle)
{
  InArg in;
  OutArg out;
  struct_in8 in8;
  struct_out8 out8;

  memset(&in,0,sizeof(in));
  memset(&out,0,sizeof(out));
  memset(&out8,0,sizeof(out8));

  in.in_struct = &in8;
  out.out_struct = &out8;
  int* messageHandles[2] = {textMessageHandle, editorMessageHandle};
  in8.Settings = (void*)messageHandles;
  in8.Version = GATE_WINDOW_HANDLES;

  DWORD error = function(8,&in,&out);
  throw_if_error(error, "set_handles_for_os_message_queue");
  throw_if_error(out.ErrorCode, "set_handles_for_os_message_queue");
}


void terminal_login(Gate_Call_Sb_Kernel function, const char* cashierName)
{
  InArg in;
  OutArg out;
  struct_in9 in9;
  struct_out9 out9;

  memset(&in,0,sizeof(in));
  memset(&out,0,sizeof(out));
  memset(&out9,0,sizeof(out9));

  in.in_struct = &in9;
  out.out_struct = &out9;

  strncpy_s(in9.UserName, cashierName, sizeof(in9.UserName));

  DWORD error = function(9,&in,&out);
  throw_if_error(error, "terminal_login");
  throw_if_error(out.ErrorCode, "terminal_login");
}

void show_tech_menu_on_terminal(Gate_Call_Sb_Kernel function)
{
  InArg in;
  OutArg out;

  memset(&in,0,sizeof(in));
  memset(&out,0,sizeof(out));

  DWORD error = function(10,&in,&out);
  throw_if_error(error, "show_tech_menu_on_terminal");
  throw_if_error(out.ErrorCode, "show_tech_menu_on_terminal");
}

void enable_reader(Gate_Call_Sb_Kernel function, HWND wnd, UINT message)
{
  InArg in;
  OutArg out;
  struct_in11 in11;

  memset(&in,0,sizeof(in));
  memset(&out,0,sizeof(out));

  in.in_struct = &in11;

  in11.hWindow = (DWORD)wnd;
  in11.Message = message;

  DWORD error = function(11,&in,&out);
  throw_if_error(error, "enable_reader");
  if (out.ErrorCode == DRV_NOTSUPP)
  {
    std::cout << "Function 'Enable Reader' not supported. I think it's not a vending solution." << std::endl;
    return;
  }
  throw_if_error(out.ErrorCode, "enable_reader");
}

void disable_reader(Gate_Call_Sb_Kernel function)
{
  InArg in;
  OutArg out;

  memset(&in,0,sizeof(in));
  memset(&out,0,sizeof(out));

  DWORD error = function(12,&in,&out);
  throw_if_error(error, "disable_reader");
  throw_if_error(out.ErrorCode, "disable_reader");
}

void test_pinpad(Gate_Call_Sb_Kernel function)
{
  InArg in;
  OutArg out;
  struct_in13 in13;
  struct_out13 out13;

  memset(&in,0,sizeof(in));
  memset(&out,0,sizeof(out));
  memset(&in13,0,sizeof(in13));
  memset(&out13,0,sizeof(out13));

  in.in_struct = &in13;
  out.out_struct = &out13;

  DWORD error = function(13,&in,&out);
  throw_if_error(error, "test_pinpad");
  throw_if_error(out.ErrorCode, "test_pinpad");

  std::cout << "Serial number '" << out13.SN << "'" << std::endl;
  std::cout << "UPOS " << int(out13.Version) << "." << int(out13.Release) << "." << int(out13.Build) << std::endl;
}

std::string get_error_text(Gate_Call_Sb_Kernel function, WORD error_code)
{
  InArg in;
  OutArg out;

  struct_in14 in14;
  struct_out14 out14;

  memset(&in,0,sizeof(in));
  memset(&in14,0,sizeof(in14));

  memset(&out,0,sizeof(out));
  memset(&out14,0,sizeof(out14));

  in.in_struct = &in14;
  out.out_struct = &out14;

  in14.dwErrorCode = error_code;

  DWORD error = function(14,&in,&out);
  throw_if_error(error, "get_error_text");
  throw_if_error(out.ErrorCode, "get_error_text");
  return std::string(out14.ErrorDescription);
}

void get_terminal_merchant(Gate_Call_Sb_Kernel function)
{
  InArg in;
  OutArg out;
  struct_out15 out15;
  struct_out_reserved15 res15;
  memset(&in,0,sizeof(in));
  memset(&out,0,sizeof(out));
  memset(&out15,0,sizeof(out15));
  memset(&res15,0,sizeof(res15));

  out.out_struct = &out15;
  out.Reserved = &res15;

  DWORD error = function(15,&in,&out);
  throw_if_error(error, "get_terminal_merchant");
  throw_if_error(out.ErrorCode, "get_terminal_merchant");

  std::cout << "Terminal id '" << out15.TerminalID << "'" << std::endl;
  std::cout << "Merchant id '" << res15.MerchantID << "'" << std::endl;
}

void abort_operation(Gate_Call_Sb_Kernel function)
{
  InArg in;
  OutArg out;

  memset(&in,0,sizeof(in));
  memset(&out,0,sizeof(out));

  DWORD error = function(16,&in,&out);
  if(error == ERR_FUNCTION_NOT_FOUND)
  {
    std::cout << "abort_operation not supported by pci-dss solution" << std::endl;
    return;
  }
  throw_if_error(error, "abort_operation");
  throw_if_error(out.ErrorCode, "abort_operation");
}

void call_pilot_command(Gate_Call_Sb_Kernel function, const char* command)
{
  InArg in;
  OutArg out;

  memset(&in,0,sizeof(in));

  in.Reserved = (void*)command;

  memset(&out,0,sizeof(out));

  DWORD error = function(17,&in,&out);
  throw_if_error(error, "call_pilot_command");
  throw_if_error(out.ErrorCode, "call_pilot_command");
}

void set_ini_parameters(Gate_Call_Sb_Kernel function, const char* pairs)
{
  InArg in;
  OutArg out;

  memset(&in,0,sizeof(in));
  memset(&out,0,sizeof(out));

  in.Reserved = (void*)pairs;
  DWORD error = function(19,&in,&out);
  throw_if_error(error, "set_ini_parameters");
  throw_if_error(out.ErrorCode, "set_ini_parameters");
}

void read_monitoring_message_and_erase(Gate_Call_Sb_Kernel function)
{
  InArg in;
  OutArg out;

  struct_in22 in22;
  struct_out22 out22;

  memset(&in,0,sizeof(in));
  memset(&in22,0,sizeof(in22));

  memset(&out,0,sizeof(out));
  memset(&out22,0,sizeof(out22));

  in.in_struct = &in22;
  out.out_struct = &out22;
  in22.bErase = true;

  DWORD error = function(22,&in,&out);
  throw_if_error(error, "read_monitoring_message_and_erase");
  if (out.ErrorCode == ERR_PIL_NO_FILES) {
    std::cout << "Monitoring message not received." << std::endl;
    return;
  }
  throw_if_error(out.ErrorCode, "read_monitoring_message_and_erase");
  std::cout << "Monitoring message '"<< out22.msg <<"'." << std::endl;
}

void read_llt_id(Gate_Call_Sb_Kernel function)
{
  InArg in;
  OutArg out;

  struct_in27 in27;
  struct_out27 out27;

  memset(&in,0,sizeof(in));
  memset(&in27,0,sizeof(in27));

  memset(&out,0,sizeof(out));
  memset(&out27,0,sizeof(out27));

  std::vector<char> buf;

  in27.DataSize = 16;
  buf.resize(in27.DataSize);
  out27.Data = buf.data();

  in.in_struct = &in27;
  out.out_struct = &out27;

  DWORD error = function(27,&in,&out);

  throw_if_error(error, "error read loalty list"); 

  if (out.ErrorCode == ERR_MORE_DATA)
  {
    in27.DataSize = out27.Size + 1;
    buf.resize(in27.DataSize);
    out27.Data = buf.data();
    error = function(27, &in, &out);
  }

  throw_if_error(error, "error read loalty list");
  throw_if_error(out.ErrorCode, "error read loalty list");  
  std::cout << "loyalty id list:"<< buf.data() <<std::endl;
}


const char* SBPILOT_COMMAND_PRINT_HELP = " 36";

int main (int argc, char** argv)
{
  setlocale(LC_ALL, "russian_Russia.1251");
  HMODULE dll = LoadLibrary("gate.dll");
  if (!dll) 
  {
    std::cerr << "gate.dll not found";
    return -1;
  }

  Gate_Call_Sb_Kernel gate_Call_Sb_Kernel = (Gate_Call_Sb_Kernel)GetProcAddress(dll, CALL_SB_KERNEL_NAME);
  if (!gate_Call_Sb_Kernel)
  {
    std::cerr << "function _call_sb_kernel in gate.dll not found.";
    return -2;
  }

  try {

    /* 0 */ initialize(gate_Call_Sb_Kernel);

    /* 2 */ DWORD functions_count = get_functions_count(gate_Call_Sb_Kernel);
    assert(functions_count);
    std::cout << "gate.dll has " << functions_count << " functions." << std::endl;

    EnumStruct* functions = new EnumStruct[functions_count];
    /* 3 */ get_functions_list(gate_Call_Sb_Kernel, functions);
    for(DWORD n = 0; n < functions_count; ++n)
    {
      std::cout << "Function: '"<< functions[n].Name << "' has number " << functions[n].FuncID << std::endl;
    }
    delete [] functions;

    /* 17 */ call_pilot_command(gate_Call_Sb_Kernel, SBPILOT_COMMAND_PRINT_HELP);

    /* 5 */DWORD chequeSize = get_cheque_size(gate_Call_Sb_Kernel);
    if (chequeSize)
    {
      char* cheque = new char[chequeSize];
      get_last_cheque(gate_Call_Sb_Kernel, cheque);
      std::cout << "=========CHEQUE START=======================" << std::endl;
      std::cout << cheque << std::endl;
      std::cout << "=========CHEQUE END=========================" << std::endl;
      delete [] cheque;
    }

    /* 6 */ DWORD currencyCount = get_currency_count(gate_Call_Sb_Kernel);
    std::cout << "Terminal has " << currencyCount << " currencies in parameters now." << std::endl;
    if (currencyCount)
    {
      CurrDesc* currencies = new CurrDesc[currencyCount];
      /* 7 */ get_currencies(gate_Call_Sb_Kernel, currencies);
      delete [] currencies;
    }

    /* 8 */set_handles_for_os_message_queue(gate_Call_Sb_Kernel, NULL, NULL);
    /* 9 */terminal_login(gate_Call_Sb_Kernel, "Ivan Ivanov");
    /* 10*/show_tech_menu_on_terminal(gate_Call_Sb_Kernel);
    /* 11*/enable_reader(gate_Call_Sb_Kernel, NULL, 0);
    /* 12*/disable_reader(gate_Call_Sb_Kernel);
    /* 13*/test_pinpad(gate_Call_Sb_Kernel);
    /* 14*/std::cout << "error code 2000 has text " << get_error_text(gate_Call_Sb_Kernel, 2000).c_str() << std::endl;
    /* 15*/get_terminal_merchant(gate_Call_Sb_Kernel);
    /* 16*/abort_operation(gate_Call_Sb_Kernel);
    /* 19*/set_ini_parameters(gate_Call_Sb_Kernel,"FirstParametr=FirstValue;SecondParametr=SecondValue");
    /* 22*/read_monitoring_message_and_erase(gate_Call_Sb_Kernel);
    /* 27*/read_llt_id(gate_Call_Sb_Kernel);
    /* 1*/ de_initialize(gate_Call_Sb_Kernel);
  }
  catch(const Error& error)
  {
    std::cerr << "Error '" << error.Code << ":" << error.Text.c_str() << "' on line " << error.Line << std::endl;
    de_initialize(gate_Call_Sb_Kernel);
  }

  FreeLibrary(dll);
  return 0;
}
