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

#include "..\..\..\..\errors.h"
#include "..\..\..\..\gate.h"
#include "..\..\..\..\sbkernel.h"
#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");
}

DWORD get_vas_xml(Gate_Call_Sb_Kernel function)
{

  class VasHolder
  {
  public:
    VasHolder(DWORD Size)
    {
      s=new char[Size];
    }
    ~VasHolder()
    {
      delete [] s;
    }
    char* s;
    operator char*(){return s;}
  };

  InArg in;
  OutArg out;

  std::cout << "INFOPF_VAS bit is set. Get xml size..." << std::endl;

  struct_out25 out25;
  struct_in26  in26;
  struct_out26 out26;
  memset(&in, 0, sizeof(in));
  memset(&out, 0, sizeof(out));
  memset(&out25, 0, sizeof(out25));
  memset(&out26, 0, sizeof(out26));
  memset(&in26,  0, sizeof(in26));
  out.out_struct = &out25;

  DWORD error = function(KO_GET_VAS_SIZE /*25*/,&in,&out);

  throw_if_error(error, "read_vas_size");
  throw_if_error(out.ErrorCode, "read_vas_size");

  std::cout << "Vas string size is " << out25.Size << std::endl;
  out26.Size=out25.Size+1;

  VasHolder sh(out26.Size);
  out26.VasXml=sh;
  out.out_struct = &out26;
  //do not erase vas string
  in26.EraseFlag=VAS_NONE;

  in.in_struct   = &in26;    
  error = function(KO_GET_VAS_STRING /*26*/,&in,&out);    
  throw_if_error(error, "read_vas_string");
  throw_if_error(out.ErrorCode, "read_vas_string");
  std::cout << "Vas string: " << std::endl << out26.VasXml << std::endl;
  return out.ErrorCode;
}

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

  InDetail.Amount = amount;
  InDetail.CardType=CT_VAS_AND_PAY;

  std::cout << "Make payment with simultaneous reading of VAS." << std::endl;
  
  
  DWORD error = function(KO_SALE,&in,&out);

  if (error==ERR_OK)
    error=out.ErrorCode;

  if (error==ERR_THIS_IS_SBER)
  {
    std::cout << "Vas || spasibo || loyalty program detected!" << std::endl;
    if (out_res.Flags & INFOPF_VAS)
    {
      error = get_vas_xml(function);
    }
    else
    {
      std::cout << "No VAS data." << std::endl;
    }

    std::cout << "Payment app response"<<std::endl;
    std::cout << "IsOwn:"<<out_res.IsOwn<< std::endl;
    std::cout << "LltId:"<<out_res.LltID<< std::endl;
    std::cout << "Hash:"<<out_res.Cert<< std::endl;
    std::cout << " New amount:"<<secondAmount<<std::endl;
    InDetail.Amount = secondAmount;
    InDetail.CardType=CT_USER;//disable VAS
    strcpy_s(InDetail.Track2, (const char*)out_res.Cert); // COPY HASH from Cert to Track2

    error = function(KO_SALE,&in,&out);
  
  }

  throw_if_error(error, "perform_sale");
  throw_if_error(out.ErrorCode, "perform_sale");
}

void perform_refund(Gate_Call_Sb_Kernel function, DWORD amount)
{
  InArg in;
  OutArg out;
  struct_in_cl4 InDetail;
  struct_out_cl4 OutDetail;
  struct_out_reserved4 out_res;

  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;
  memset(&out_res, 0, sizeof(out_res));
  out_res.size=sizeof(out_res);
  
  out.out_struct = &OutDetail;
  out.Reserved = &out_res;

  InDetail.Amount = amount;
  InDetail.CardType=CT_VAS_AND_PAY;

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

  if (error==ERR_OK)
    error=out.ErrorCode;

  if (error==ERR_OK)
  {
    if (out_res.Flags & INFOPF_VAS)
    {
      error = get_vas_xml(function);
    }
    else
    {
      std::cout << "No VAS data." << std::endl;
    }  
  }

  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 read_vas(Gate_Call_Sb_Kernel function)
{

  class VasHolder
  {
  public:
    VasHolder(DWORD Size)
    {
      s=new char[Size];
    }
    ~VasHolder()
    {
      delete [] s;
    }
    char* s;
    operator char*(){return s;}
  };

  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;
  //wait only vas
  m_in.CardType=CT_VAS_ONLY;
  //m_in.CardType=CT_VAS_AND_PAY;
  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 Google VAS!" << std::endl;
  DWORD error = function(KO_GET_CARD_ID /*5002*/,&in,&out);
  throw_if_error(error, "read_vas");
  throw_if_error(out.ErrorCode, "read_vas");

  if (out_res.Flags & INFOPF_VAS)
  {
    error = get_vas_xml(function);
  }
  else
  {
    std::cout << "No VAS data." << std::endl;
  }

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

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

    /* 5002*/ read_vas(gate_Call_Sb_Kernel);
    return 0;
    /* 4000 */perform_sale(gate_Call_Sb_Kernel, 1000, 900);

    /* 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);
	

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