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

#include "..\..\..\..\..\..\..\..\src\include\public\errors.h"
#include "..\..\..\..\..\..\..\..\src\include\public\gate.h"
#include "..\..\..\..\..\..\..\..\src\include\public\sbkernel.h"
#include "..\..\..\..\..\..\..\..\src\include\cardtype.h"

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

  int i = __LINE__;

  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(0,&in,&out);
  throw_if_error(error, "initialization");
  throw_if_error(out.ErrorCode, "initialization");
}

void perform_sale(Gate_Call_Sb_Kernel function, DWORD amount)
{
  InArg in;
  OutArg out;
  struct_in_cl4 InDetail;
  struct_out_cl4 OutDetail;
  memset(&in, 0, sizeof(in));
  memset(&InDetail, 0, sizeof(InDetail));
  in.in_struct = &InDetail;
  memset(&out, 0, sizeof(out));
  memset(&OutDetail, 0, sizeof(OutDetail));
  out.out_struct = &OutDetail;

  InDetail.Amount = amount;

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

void perform_sale_with_payment_req(Gate_Call_Sb_Kernel function, DWORD amount, char* vTagValue, char* vPaymentTag)
{
  InArg in;
  OutArg out;
  struct_in_cl4 InDetail;
  struct_out_cl4 OutDetail;
  struct_in_reserved4 inExtra;
  struct_out_reserved4 outExtra;


  memset(&in, 0, sizeof(in));
  memset(&InDetail, 0, sizeof(InDetail));
  memset(&inExtra, 0, sizeof(inExtra));
  in.in_struct = &InDetail;
  
  memset(&out, 0, sizeof(out));
  memset(&OutDetail, 0, sizeof(OutDetail));
  memset(&outExtra, 0, sizeof(outExtra));
  out.out_struct = &OutDetail;
  out.Reserved = &outExtra;
  outExtra.size = sizeof(outExtra);

  InDetail.Amount = amount;

  inExtra.size = sizeof(inExtra);
  if (vTagValue)
	strcpy(inExtra.TagValue, vTagValue);
  if (vPaymentTag)
	strcpy(inExtra.PaymentTag, vPaymentTag);
  in.Reserved = &inExtra;
  
  DWORD error = function(KO_SALE,&in,&out);
  throw_if_error(error, "perform_sale_with_payment_req");
  throw_if_error(out.ErrorCode, "perform_sale_with_payment_req");
}

void perform_refund(Gate_Call_Sb_Kernel function, DWORD amount)
{
  InArg in;
  OutArg out;
  struct_in_cl4 InDetail;
  struct_out_cl4 OutDetail;
  memset(&in, 0, sizeof(in));
  memset(&InDetail, 0, sizeof(InDetail));
  in.in_struct = &InDetail;
  memset(&out, 0, sizeof(out));
  memset(&OutDetail, 0, sizeof(OutDetail));
  out.out_struct = &OutDetail;

  InDetail.Amount = amount;

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


void perform_cancel_last_operation(Gate_Call_Sb_Kernel function, DWORD amount)
{
  InArg in;
  OutArg out;
  struct_in_cl4 InDetail;
  struct_out_cl4 OutDetail;
  memset(&in, 0, sizeof(in));
  memset(&InDetail, 0, sizeof(InDetail));
  in.in_struct = &InDetail;
  memset(&out, 0, sizeof(out));
  memset(&OutDetail, 0, sizeof(OutDetail));
  out.out_struct = &OutDetail;

  InDetail.Amount = amount;

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

void perform_cancel_with_interaction(Gate_Call_Sb_Kernel function)
{
  InArg in;
  OutArg out;
  struct_in_cl4 InDetail;
  struct_out_cl4 OutDetail;
  memset(&in, 0, sizeof(in));
  memset(&InDetail, 0, sizeof(InDetail));
  in.in_struct = &InDetail;
  memset(&out, 0, sizeof(out));
  memset(&OutDetail, 0, sizeof(OutDetail));
  out.out_struct = &OutDetail;

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

void perform_preauth(Gate_Call_Sb_Kernel function, DWORD amount, std::string& rrn)
{
  InArg in;
  OutArg out;
  struct_in_cl4 InDetail;
  struct_out_cl4 OutDetail;
  struct_in_reserved4 inExtra;
  struct_out_reserved4 outExtra;

  memset(&in, 0, sizeof(in));
  memset(&InDetail, 0, sizeof(InDetail));
  memset(&inExtra, 0, sizeof(inExtra));
  memset(&out, 0, sizeof(out));
  memset(&OutDetail, 0, sizeof(OutDetail));
  memset(&outExtra, 0, sizeof(outExtra));

  in.in_struct = &InDetail;
  out.out_struct = &OutDetail;
  out.Reserved = &outExtra;
  outExtra.size = sizeof(outExtra);

  InDetail.Amount = amount;
  if (!rrn.empty())
  {
    inExtra.size = sizeof(inExtra);
    strcpy_s(inExtra.RRN, rrn.c_str());
    in.Reserved = &inExtra;
  }

  DWORD error = function(KO_PREAUTH,&in,&out);
  throw_if_error(error, "perform_preauth");
  throw_if_error(out.ErrorCode, "perform_preauth");
  rrn = std::string(OutDetail.RRN);
}

void complete_preauth(Gate_Call_Sb_Kernel function, DWORD amount, std::string& rrn)
{
  InArg in;
  OutArg out;
  struct_in_cl4 InDetail;
  struct_out_cl4 OutDetail;
  struct_in_reserved4 inExtra;
  struct_out_reserved4 outExtra;

  memset(&in, 0, sizeof(in));
  memset(&InDetail, 0, sizeof(InDetail));
  memset(&inExtra, 0, sizeof(inExtra));
  memset(&out, 0, sizeof(out));
  memset(&OutDetail, 0, sizeof(OutDetail));
  memset(&outExtra, 0, sizeof(outExtra));

  in.in_struct = &InDetail;
  out.out_struct = &OutDetail;
  out.Reserved = &outExtra;
  outExtra.size = sizeof(outExtra);

  InDetail.Amount = amount;
  if (!rrn.empty())
  {
    inExtra.size = sizeof(inExtra);
    strcpy_s(inExtra.RRN, rrn.c_str());
    in.Reserved = &inExtra;
  }

  DWORD error = function(KO_COMPLETE_AUTH,&in,&out);
  throw_if_error(error, "complete_preauth");
  throw_if_error(out.ErrorCode, "complete_preauth");
  rrn = std::string(OutDetail.RRN);
}

void perform_multyauthorization_and_complete(Gate_Call_Sb_Kernel function)
{
    std::string rrn;
    DWORD amountA = 20000, amountB = 30000;
    perform_preauth (function, amountA, rrn);
    perform_preauth (function, amountB, rrn);
    complete_preauth(function, amountA + amountB, rrn);
}

void perform_multyauthorization_and_cancel(Gate_Call_Sb_Kernel function)
{
    std::string rrn;
    DWORD amountA = 20000, amountB = 30000;
    perform_preauth (function, amountA, rrn);
    perform_preauth (function, amountB, rrn);
    complete_preauth(function, 0, rrn);
}

void perform_multyauthorization_and_cancel_complete(Gate_Call_Sb_Kernel function)
{
    std::string rrn;
    DWORD amountA = 10000, amountB = 20000, amountC = 90000;
    perform_preauth (function, amountA, rrn);
    perform_preauth (function, amountB, rrn);
    perform_preauth (function, amountC, rrn);
    perform_cancel_last_operation (function, amountC);
    complete_preauth(function, amountA + amountB, rrn);
}

void perform_open_reader(Gate_Call_Sb_Kernel function)
{
  InArg in;
  OutArg out;
  struct_in_cl4 InDetail;
  struct_out_cl4 OutDetail;
  memset(&in, 0, sizeof(in));
  memset(&InDetail, 0, sizeof(InDetail));
  in.in_struct = &InDetail;
  memset(&out, 0, sizeof(out));
  memset(&OutDetail, 0, sizeof(OutDetail));
  out.out_struct = &OutDetail;


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

void perform_close_reader(Gate_Call_Sb_Kernel function)
{
  InArg in;
  OutArg out;
  struct_in_cl4 InDetail;
  struct_out_cl4 OutDetail;
  memset(&in, 0, sizeof(in));
  memset(&InDetail, 0, sizeof(InDetail));
  in.in_struct = &InDetail;
  memset(&out, 0, sizeof(out));
  memset(&OutDetail, 0, sizeof(OutDetail));
  out.out_struct = &OutDetail;

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

bool perform_test_reader(Gate_Call_Sb_Kernel function)
{
  InArg in;
  OutArg out;
  struct_in_cl4 InDetail;
  struct_out_cl4 OutDetail;
  memset(&in, 0, sizeof(in));
  memset(&InDetail, 0, sizeof(InDetail));
  in.in_struct = &InDetail;
  memset(&out, 0, sizeof(out));
  memset(&OutDetail, 0, sizeof(OutDetail));
  out.out_struct = &OutDetail;

  DWORD error = function(KO_TEST_READER,&in,&out);
  throw_if_error(error, "perform_test_reader");
  if (out.ErrorCode == ERR_CARD_READY)
    return true;
  else
  if (out.ErrorCode == SPS_PROCESSING)
    return false;
  throw_if_error(out.ErrorCode, "perform_test_reader");
  return false;
}

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  hex_2_ascii(const BYTE *source,BYTE *destn, WORD sz)
{
  BYTE  a, c;
  WORD i,j;

  for(i=0,j=0;sz--;){
    c = source[j];
    a = (BYTE)(c>>4);
    if (a <= 9)
      destn[i++] = (BYTE)(a + '0');
    else
      destn[i++] = (BYTE)(a + '7');

    c = source[j++];
    a = (BYTE)(c&0x0f);
    if (a <= 9)
      destn[i++] = (BYTE)(a + '0');
    else
    destn[i++] = (BYTE)(a + '7');
  }
  destn[i] = '\0';
}

DWORD read_card_hash(Gate_Call_Sb_Kernel function, std::string& hash)
{
  InArg in;
  OutArg out;
  struct_in_cl5 m_in;
  struct_out_cl5 m_out;
  struct_in_reserved5  m_res;
  struct_out_reserved5 out_res;

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

  memset(&out,0,sizeof(out));
  memset(&m_out,0,sizeof(m_out));
  memset(&out_res,0,sizeof(out_res));
  out.out_struct = &m_out;
  out.Reserved = &out_res;
  out_res.size = sizeof(out_res);
  std::cout << "Let's read hash!" << std::endl;
  DWORD error = function(5002,&in,&out);
  throw_if_error(error, "read_card_hash");
  throw_if_error(out.ErrorCode, "read_card_hash");

  hash.resize(40);
  hex_2_ascii(out_res.Hash, (BYTE*)&hash[0], sizeof(out_res.Hash));
  std::cout << "Hash: '" << hash.c_str() << "'"<< std::endl;
  return out.ErrorCode;
}


DWORD card_holder_input_phone(Gate_Call_Sb_Kernel function)
{
  InArg in;
  OutArg out;
  struct_in_cl5 m_in;
  struct_out_cl5 m_out;
  struct_in_reserved5  m_res;
  struct_out_reserved5 out_res;

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

  memset(&out,0,sizeof(out));
  memset(&m_out,0,sizeof(m_out));
  memset(&out_res,0,sizeof(out_res));
  out.out_struct = &m_out;
  out.Reserved = &out_res;
  out_res.size = sizeof(out_res);
  std::cout << "Let's ask user about phone number!" << std::endl;
  m_res.bEditMode |= TVM_NEED_PHONE;
  DWORD error = function(5007,&in,&out);
  throw_if_error(error, "card_holder_input_phone");
  throw_if_error(out.ErrorCode, "card_holder_input_phone");

  return out.ErrorCode;
}

DWORD card_holder_input_email(Gate_Call_Sb_Kernel function)
{
  InArg in;
  OutArg out;
  struct_in_cl5 m_in;
  struct_out_cl5 m_out;
  struct_in_reserved5  m_res;
  struct_out_reserved5 out_res;

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

  memset(&out,0,sizeof(out));
  memset(&m_out,0,sizeof(m_out));
  memset(&out_res,0,sizeof(out_res));
  out.out_struct = &m_out;
  out.Reserved = &out_res;
  out_res.size = sizeof(out_res);
  std::cout << "Let's ask user about email!" << std::endl;
  m_res.bEditMode |= TVM_NEED_EMAIL;
  DWORD error = function(5007,&in,&out);
  throw_if_error(error, "card_holder_input_email");
  throw_if_error(out.ErrorCode, "card_holder_input_email");

  return out.ErrorCode;
}

void use_hibrid_read(Gate_Call_Sb_Kernel function)
{
    perform_open_reader(function);
    if (perform_test_reader(function)) {
      std::cout << "Card is present in reader" << std::endl;
      // .. perform 
    }
    else
      std::cout << "Card is not present in reader" << std::endl;
    perform_close_reader(function);
}

DWORD ShowQrCodeDisplay(Gate_Call_Sb_Kernel function)
{
  InArg in;
  OutArg out;
  struct_in_cl5 m_in;
  struct_out_cl5 m_out;
  struct_in_reserved5  m_res;
  struct_out_reserved5 out_res;

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

  memset(&out,0,sizeof(out));
  memset(&m_out,0,sizeof(m_out));
  memset(&out_res,0,sizeof(out_res));
  out.out_struct = &m_out;
  out.Reserved = &out_res;
  out_res.size = sizeof(out_res);
  strncpy(m_res.TextMessage, "https://sberbank.ru/qr/?dynamicQr=5a50532a20d44acd814a757ae12008a6", sizeof(m_res.TextMessage));
  DWORD error = function(5010,&in,&out);
  throw_if_error(error, "PAY QR");
  throw_if_error(out.ErrorCode, "PAY QR");

  return out.ErrorCode;
}

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

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

    /* 0 */ initialize(gate_Call_Sb_Kernel);

    /* 4000 */perform_sale(gate_Call_Sb_Kernel, 1000);
	/* 4000 */perform_sale_with_payment_req(gate_Call_Sb_Kernel, 10000, "10427589063TYWF22561F", NULL);
    /* 4002*/ perform_refund(gate_Call_Sb_Kernel, 2000);
    /* 4003*/ perform_cancel_last_operation(gate_Call_Sb_Kernel, 2000);
    /* 4003*/ perform_cancel_with_interaction(gate_Call_Sb_Kernel);
    /* 4009*/ perform_multyauthorization_and_complete(gate_Call_Sb_Kernel);
    /* 4009*/ perform_multyauthorization_and_cancel(gate_Call_Sb_Kernel);
    perform_multyauthorization_and_cancel_complete(gate_Call_Sb_Kernel);
    std::string hash;
    /* 5002*/ read_card_hash(gate_Call_Sb_Kernel, hash);
    std::cout << "Card hash:" << hash.c_str() << std::endl;
    use_hibrid_read(gate_Call_Sb_Kernel);
	
    card_holder_input_phone(gate_Call_Sb_Kernel);
    card_holder_input_email(gate_Call_Sb_Kernel);	
    ShowQrCodeDisplay(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;
  }

  FreeLibrary(dll);
  return 0;
}
