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

#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, "initialize");
  throw_if_error(out.ErrorCode, "initialize");
  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;
}

bool IsCardHolderWantsToPayByCash()
{
  return false; // change it to true is customer wants to pay by cash
}

void spasibo(Gate_Call_Sb_Kernel function, std::string& hash, std::string& rrn)
{
  InArg in;
  OutArg out;
  struct_in_cl4 m_in;
  struct_out_cl4 m_out;
  struct_in_reserved4  inExtra;
  struct_out_reserved4 outExtra;

  memset(&in,0,sizeof(in));
  memset(&m_in,0,sizeof(m_in));
  memset(&inExtra,0,sizeof(inExtra));
  in.in_struct = &m_in;
  in.Reserved = &inExtra;
  inExtra.size = sizeof(inExtra);

  memset(&out,0,sizeof(out));
  memset(&m_out,0,sizeof(m_out));
  memset(&outExtra,0,sizeof(outExtra));
  out.out_struct = &m_out;
  out.Reserved = &outExtra;
  outExtra.size = sizeof(outExtra);

  inExtra.Department = DEPARTMENT_NOT_SPECIFIED;
  m_in.Amount = 100000;
  std::cout << "Let's try to pay!" << std::endl;
  DWORD error = function(4000,&in,&out);
  throw_if_error(error, "spasibo");
  if (out.ErrorCode == 4353)
  {
    if (IsCardHolderWantsToPayByCash())
    {
      char close_reader_command_text[15] = "0 31";
      std::cout << "Hash of card is " << outExtra.Cert << std::endl;
      memset(&in, 0, sizeof(in));
      memset(&out, 0, sizeof(out));
      in.Reserved=static_cast<char*>(close_reader_command_text);
      error = function(17,&in,&out);
      throw_if_error(error, "close_reader_command_text");
    }
    else
    {
      std::cout << "Sberbank Spasibo detected!" << std::endl;
      m_in.Amount = 90000; // amount was changed
      strcpy_s(m_in.Track2, (const char*)outExtra.Cert); // COPY HASH from Cert to Track2
      error = function(4000,&in,&out);
      throw_if_error(error, "spasibo");
      rrn = std::string(m_out.RRN);
      hash = std::string((char*)outExtra.Cert);
    }
  }

  throw_if_error(out.ErrorCode, "spasibo");
}

void refund_without_card(Gate_Call_Sb_Kernel function, std::string& rrn, std::string& refundHash)
{
  InArg in;
  OutArg out;
  struct_in_cl4 m_in;
  struct_out_cl4 m_out;
  struct_in_reserved4  inExtra;
  struct_out_reserved4 outExtra;

  memset(&in,0,sizeof(in));
  memset(&m_in,0,sizeof(m_in));
  memset(&inExtra,0,sizeof(inExtra));
  in.in_struct = &m_in;
  in.Reserved = &inExtra;
  inExtra.size = sizeof(inExtra);

  memset(&out,0,sizeof(out));
  memset(&m_out,0,sizeof(m_out));
  memset(&outExtra,0,sizeof(outExtra));
  out.out_struct = &m_out;
  out.Reserved = &outExtra;
  outExtra.size = sizeof(outExtra);

  inExtra.Department = DEPARTMENT_NOT_SPECIFIED;

  m_in.Amount = 9000;
  strcpy(m_in.Track2, "QSELECT");
  strcpy(inExtra.RRN, rrn.c_str());
  DWORD error = function(4002,&in,&out);
  throw_if_error(error, "refund_without_card");
  refundHash = std::string((char*)outExtra.Cert);
}

#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;
  }

  try {
    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;
    }

    initialize(gate_Call_Sb_Kernel);
    
    std::string hash, rrn, refundHash;
    spasibo(gate_Call_Sb_Kernel, hash, rrn);
    refund_without_card(gate_Call_Sb_Kernel, rrn, refundHash);
    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;
  }

  FreeLibrary(dll);
  return 0;
}
