#include <windows.h>
#include <iostream>
#include <cstdlib>
#include <ctime>

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

#ifndef WINCE
  #define CALL_SB_KERNEL_NAME "_call_sb_kernel"
  #define DDL_NAME "gate.dll"
#else
  #define CALL_SB_KERNEL_NAME L"call_sb_kernel"
  #define DDL_NAME L"gate.dll"
#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;
}

void perform_sale(Gate_Call_Sb_Kernel function, DWORD amount, bool& hasDocument,DWORD& requestId)
{
  InArg in;
  OutArg out;
  struct_in_cl4 InDetail;
  struct_out_cl4 OutDetail;
  struct_in_reserved4 in_extra;
  struct_out_reserved4 out_extra;

  memset(&in, 0, sizeof(in));
  memset(&InDetail, 0, sizeof(InDetail));
  memset(&in_extra, 0, sizeof(in_extra));
  in.in_struct = &InDetail;
  in.Reserved = &in_extra;
  in_extra.size = sizeof(in_extra);

  memset(&out, 0, sizeof(out));
  memset(&OutDetail, 0, sizeof(OutDetail));
  memset(&out_extra, 0, sizeof(out_extra));
  out.out_struct = &OutDetail;
  out.Reserved = &out_extra;
  out_extra.size = sizeof(out_extra);

  InDetail.Amount = amount;
  in_extra.RequestID = requestId;

  DWORD error = function(KO_SALE,&in,&out);
  if (out_extra.RequestID != requestId) {
    if (out_extra.RequestID)
      std::cerr << "WARNING! RequestId is not the same. External RequestId not supported by current UPOS version!" << std::endl;
    else
      std::cerr << "WARNING! RequestId not supported! by command" << KO_SALE << std::endl;
    requestId = out_extra.RequestID;
  }
  throw_if_error(error, "perform_sale");
  throw_if_error(out.ErrorCode, "perform_sale");
  hasDocument = (out.Flags&F_HAVE_DOCUMENT) == F_HAVE_DOCUMENT;
}

void check_operation_status(Gate_Call_Sb_Kernel function, bool& hasDocument, DWORD requestId, bool& pinpadUnavailable)
{
  InArg in;
  OutArg out;
  struct_in_cl6 InDetail;
  struct_out_cl6 OutDetail;
  struct_in_reserved6 in_extra;

  memset(&in, 0, sizeof(in));
  memset(&InDetail, 0, sizeof(InDetail));
  memset(&in_extra, 0, sizeof(in_extra));
  in.in_struct = &InDetail;
  in.Reserved = &in_extra;
  in_extra.size = sizeof(in_extra);

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

  in_extra.dwQueryRequestID = requestId;
  pinpadUnavailable = false;
  DWORD error = function(KO_GET_STATUS_REQ_ID,&in,&out);

  throw_if_error(error, "perform_sale");
  if (out.ErrorCode == SPS_NOLINK)
    pinpadUnavailable = true;
  else
    throw_if_error(out.ErrorCode, "perform_sale");
  hasDocument = (out.Flags&F_HAVE_DOCUMENT) == F_HAVE_DOCUMENT;
}

#ifndef WINCE
int main (int argc, char** argv)
#else
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nShowCmd)
#endif
{
  HMODULE dll = LoadLibrary(DDL_NAME);
  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);
  try {
    if (!gate_Call_Sb_Kernel)
    {
      std::cerr << "function _call_sb_kernel in gate.dll not found";
      return -2;
    }

    initialize(gate_Call_Sb_Kernel);

    std::string hash;
    const DWORD STO_RUBLEY = 10000;
    srand(time(NULL));
    DWORD requestId = (DWORD)rand(); // Use only unique values during two payments days

    bool hasDocument = false;
    perform_sale(gate_Call_Sb_Kernel, STO_RUBLEY, hasDocument, requestId);
    hasDocument = false;
    bool pinpadUnavailable = false;
    do {
      check_operation_status(gate_Call_Sb_Kernel, hasDocument, requestId, pinpadUnavailable);
      if (pinpadUnavailable)
        std::cerr << "Pinpad Unavailable we cannot check operation status" << std::endl;
      else
        break;
    } while(pinpadUnavailable);
    if (hasDocument)
    {
      // print cheque
    }
    
  }
  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;
}
