////////////////////////////////////////////////////////////////////////////////
// Copyright (c) Datacard Corporation.  All Rights Reserved.
//
// XPS Printer Driver SDK: Single-wire smartcard personalization c++ sample
// for a mifare chip.
////////////////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include <bidispl.h>
#include <iostream>
#include <iomanip>
#include <sstream>
#include <atltime.h>
#include "DXP01SDK.H"
#include "DXP01SDK_SCARD.H"
#include "XGetopt.h"
#include "util.h"
#include "CommandlineOptions.h"
#include "MiFare_Command.h"
#include "MiFare_Common.h"
#include "MiFare_Response.h"
#include "SmartcardCommon.h"

void usage()
{
    const auto msgFormat = LR"(
%s demonstrates interactive mode parking a
card in the smart card station, performing single-wire smartcard functions,
moving the card from the station, and options to print and poll for job
completion.

Uses hardcoded data for printing.

%s -n <printername> [-p] [-b] [-c]

options:
  -n <printername>. Required. Try -n "XPS Card Printer".
  -p Print simple black text on one or both sides of the card depending on the
     printer used.
  -b use back of card for smartcard chip.
  -c Poll for job completion.

%s -n "XPS Card Printer"
  Parks a card in the printer smart card station, connects to the MiFare chip
  and performs some MiFare chip activities.

%s -n "XPS Card Printer" -p -c
  Parks a card in printer smart card station, moves the card from the station,
  prints simple text and graphics on the front side of the card, and polls and
  displays job status.
)";

    const CStringW shortExeName = util::GetShortExeName();
    CStringW msg;
    msg.Format(msgFormat, static_cast<LPCWSTR> (shortExeName), static_cast<LPCWSTR> (shortExeName), static_cast<LPCWSTR> (shortExeName), static_cast<LPCWSTR> (shortExeName));
    std::wcout << msg.GetBuffer() << std::endl;
    std::exit(0);
}


////////////////////////////////////////////////////////////////////////////////
//  DisplayChipInfo()
//
// get mifare status. see pg. 23, "CCID_Protocol_spec_120224_.pdf"
////////////////////////////////////////////////////////////////////////////////
void DisplayChipInfo(dxp01sdk::SCard& scard)
{
    std::cout << std::endl;

    std::stringstream exceptionText;

    dxp01sdk::MiFare_Command miFareCommand;
    std::vector <byte> getCardStatusCommand = miFareCommand.CreateGetCardStatusCommand();

    SmartcardCommon::DisplayByteVectorAsHex("DisplayChipInfo() - bytes sent", getCardStatusCommand);

    std::vector <byte> receivedBytes;

    long scardResult = scard.SCardTransmit(getCardStatusCommand, receivedBytes);
    std::cout << SmartcardCommon::SCardResultMessage(scardResult, "SCardTransmit()") << std::endl;
    if (SCARD_S_SUCCESS != scardResult) {
        exceptionText << SmartcardCommon::SCardResultMessage(scardResult, "SCardTransmit()");
        throw std::runtime_error(exceptionText.str());
    }

    if (SCARD_S_SUCCESS == scardResult && receivedBytes.size()) {
        SmartcardCommon::DisplayByteVectorAsHex("bytes received (std::hex)", receivedBytes);

        { // optional
            MiFareResponse miFareResponse(receivedBytes);
            std::cout << "mifare status OK?: " << (miFareResponse.IsStatusOK() ? "true" : "false") << std::endl;
        }
    }
}

void LoadKeys(
    dxp01sdk::SCard& scard,
    const byte sector,
    const dxp01sdk::MiFare_Command::KeyType keyType)
{
    std::cout << std::endl;
    
    std::stringstream exceptionText;

    dxp01sdk::MiFare_Common miFareCommon;
    const std::vector <byte> miFareTestKey = miFareCommon.GetMifareTestKey();

    SmartcardCommon::DisplayByteVectorAsHex("LoadKeys() - bytes sent", miFareTestKey);

    dxp01sdk::MiFare_Command miFareCommand;
    const std::vector <byte> loadKeysCommand = miFareCommand.CreateLoadKeysCommand(
        keyType,
        sector,
        miFareTestKey);

    std::vector <byte> receivedBytes;

    long scardResult = scard.SCardTransmit(loadKeysCommand, receivedBytes);
    std::cout << SmartcardCommon::SCardResultMessage(scardResult, "SCardTransmit()") << std::endl;
    if (SCARD_S_SUCCESS != scardResult) {
        exceptionText << SmartcardCommon::SCardResultMessage(scardResult, "SCardTransmit()");
        throw std::runtime_error(exceptionText.str());
    }

    if (0 == receivedBytes.size()) {
        throw std::runtime_error("no response from LoadKeys()");
    }

    SmartcardCommon::DisplayByteVectorAsHex("LoadKeys() response", receivedBytes);

    // optional:
    {
        CStringW statusCodeString = miFareCommon.StatusCodeBytesToString(receivedBytes);
        std::wcout << statusCodeString.GetBuffer() << std::endl;
        miFareCommon.CheckStatusCode(receivedBytes[0]);
    }
}

////////////////////////////////////////////////////////////////////////////////
// WriteData()
//
// write some sample data to the given block and sector.
////////////////////////////////////////////////////////////////////////////////
void WriteData(
    dxp01sdk::SCard&    scard,
    const byte          sector,
    const byte          block,
    std::vector <byte>  testDataBytes)
{
    std::cout << std::endl;

    std::stringstream exceptionText;

    dxp01sdk::MiFare_Command miFareCommand;
    const std::vector <byte> writeBlockCommand = miFareCommand.CreateWriteBlockCommand(
        dxp01sdk::MiFare_Command::A,
        sector,
        block,
        testDataBytes);

    SmartcardCommon::DisplayByteVectorAsHex("WriteData() - bytes sent", writeBlockCommand);

    std::vector <byte> receivedBytes;

    long scardResult = scard.SCardTransmit(writeBlockCommand, receivedBytes);
    std::cout << SmartcardCommon::SCardResultMessage(scardResult, "SCardTransmit()") << std::endl;
    if (SCARD_S_SUCCESS != scardResult) {
        exceptionText << SmartcardCommon::SCardResultMessage(scardResult, "SCardTransmit()");
        throw std::runtime_error(exceptionText.str());
    }

    if (0 == receivedBytes.size()) {
        throw std::runtime_error("write data failed.");
    }

    SmartcardCommon::DisplayByteVectorAsHex("Write Block response", receivedBytes);

    // optional:
    {
        dxp01sdk::MiFare_Common miFareCommon;
        CString statusCodeString = miFareCommon.StatusCodeBytesToString(receivedBytes);
        std::cout << CT2A(statusCodeString) << std::endl;
        miFareCommon.CheckStatusCode(receivedBytes[0]);
    }
}

////////////////////////////////////////////////////////////////////////////////
// ReadData()
//
// read some data from the given block and sector.
////////////////////////////////////////////////////////////////////////////////
void ReadData(
    dxp01sdk::SCard&    scard,
    const byte          sector,
    const byte          block)
{
    std::cout << std::endl;

    std::stringstream exceptionText;

    dxp01sdk::MiFare_Command myFareCommand;
    const std::vector <byte> readBlockCommand = myFareCommand.CreateReadBlockCommand(
        dxp01sdk::MiFare_Command::A,
        sector,
        block);

    SmartcardCommon::DisplayByteVectorAsHex("ReadData() - bytes sent", readBlockCommand);

    std::vector <byte> receivedBytes;

    long scardResult = scard.SCardTransmit(readBlockCommand, receivedBytes);
    std::cout << SmartcardCommon::SCardResultMessage(scardResult, "SCardTransmit()") << std::endl;
    if (SCARD_S_SUCCESS != scardResult) {
        exceptionText << SmartcardCommon::SCardResultMessage(scardResult, "SCardTransmit()");
        throw std::runtime_error(exceptionText.str());
    }

    if (0 == receivedBytes.size()) {
        throw std::runtime_error("no response from read block command");
    }

    SmartcardCommon::DisplayByteVectorAsHex("read block response", receivedBytes);

    { // optional
        dxp01sdk::MiFare_Common miFareCommon;
        CString statusCodeString = miFareCommon.StatusCodeBytesToString(receivedBytes);
        std::cout << CT2A(statusCodeString) << std::endl;
        miFareCommon.CheckStatusCode(receivedBytes[0]);
    }

    // start at byte[1]; byte[0] is zero, and there are two trailing statuscode bytes:
    CStringA receivedDataString((PCHAR) &receivedBytes[1], (int) receivedBytes.size() - 3);
    std::cout
        << "received '" << (PCHAR) receivedDataString.GetBuffer()
        << "' from sector " << (short) sector
        << ", block " << (short) block
        << std::endl;
}

std::string create_test_data_string()
{
    const CTime now(CTime::GetCurrentTime());
    const CString testDataCString(now.Format(TEXT("%Y.%m.%d.%H.%M")));
    const std::string testDataString = CT2A(testDataCString);
    return testDataString;
}

////////////////////////////////////////////////////////////////////////////////
// SmartCardControl()
////////////////////////////////////////////////////////////////////////////////
void SmartCardControl(CComPtr <IBidiSpl>& bidiSpl)
{
    std::stringstream exceptionText;
    dxp01sdk::SCard scard(bidiSpl);

    std::cout << std::endl;

    /*
         create a byte std::vector for the upcoming SCardControl() method.
         particular bytes should function with this type of contactless chip:
              Mifare 1K Classic

        The control codes below are used to turn on / off  the RF of the duali reader.
        The final byte is 0x02 for protocol contact(rf off) and 0x01 for contactless(rf on).
        It has been tested using contact connection with
          FW rev R4.1.2-3

        byte controlCode[] = { 0x82, 0x02 };  // contact

        Make contact connection using SCARD_PROTOCOL_T0
    */

    // this control code has been tested on contactless using SCARD_PROTOCOL_CL
    // FW rev R4.1.2-3
    byte controlCode[] = { 0x82, 0x01 };  // contactless / RF ON
    const std::vector <byte> controlCodeBytes(controlCode, controlCode + ARRAYSIZE(controlCode));

    SmartcardCommon::DisplayByteVectorAsHex("SCardControl bytes sent", controlCodeBytes);

    std::vector <byte> receivedBytes;

    long scardResult = scard.SCardControl(controlCodeBytes, receivedBytes);
    std::cout << SmartcardCommon::SCardResultMessage(scardResult, "SCardControl()") << std::endl;
    if (SCARD_S_SUCCESS != scardResult) {
        exceptionText << SmartcardCommon::SCardResultMessage(scardResult, "SCardControl()");
        throw std::runtime_error(exceptionText.str());
    }

    if (0 == receivedBytes.size()) {
        std::cout << "no response from SCardControl command" << std::endl;
    }
    else {
        SmartcardCommon::DisplayByteVectorAsHex("SCardControl() response", receivedBytes);
    }
}

////////////////////////////////////////////////////////////////////////////////
// PersonalizeSmartcard()
// smartcard personalization using Win32 / WinSCard.H styled wrapper class.
////////////////////////////////////////////////////////////////////////////////
void PersonalizeSmartcard(CComPtr <IBidiSpl>& bidiSpl)
{
    std::stringstream exceptionText;

    dxp01sdk::SCard scard(bidiSpl);

    // prepare for a contactless mifare chip.
    DWORD protocol = SCARD_PROTOCOL_UNDEFINED;

    long scardResult = scard.SCardConnect(dxp01sdk::SCard::contactless, protocol);
    std::cout << SmartcardCommon::SCardResultMessage(scardResult, "SCardConnect()") << std::endl;
    if (SCARD_S_SUCCESS != scardResult) {
        exceptionText << SmartcardCommon::SCardResultMessage(scardResult, "SCardConnect()");
        throw std::runtime_error(exceptionText.str());
    }

    SmartcardCommon::DisplayProtocols(protocol); // optional

    try {
        SmartcardCommon::GetSCardStatus(scard);

        DisplayChipInfo(scard);

        const byte keySector = 5;       // load "keys" from sector 5
        const byte sector = 0;          // when reading / writing use Sector = 0
        const byte block = 21;          // blocks range from 0-63
        const auto keyType = dxp01sdk::MiFare_Command::A;

        LoadKeys(scard, keySector, keyType);

        ReadData(scard, sector, block);

        const std::string testDataString = create_test_data_string();
        std::vector <byte> testDataBytes(testDataString.c_str(), testDataString.c_str() + testDataString.length());
        std::cout << "writing '" << testDataString << "' to chip." << std::endl;

        WriteData(scard, sector, block, testDataBytes);

        ReadData(scard, sector, block);

        // For Duali singlewire, the SDK cannot send smartcard commands after SmartCardControl
        // It seems like the card reader is getting reset
        SmartCardControl(bidiSpl);

    }
    catch (std::runtime_error& e) {
        scardResult = scard.SCardDisConnect(SCARD_LEAVE_CARD);
        throw std::runtime_error(e);
    }

    scardResult = scard.SCardDisConnect(SCARD_LEAVE_CARD);
    if (SCARD_S_SUCCESS != scardResult) {
        exceptionText << SmartcardCommon::SCardResultMessage(scardResult, "SCardDisConnect()");
        throw std::runtime_error(exceptionText.str());
    }

    std::cout << SmartcardCommon::SCardResultMessage(scardResult, "SCardDisConnect()") << std::endl << std::endl;
}

CommandlineOptions GetCommandLineOptions(const int argc, WCHAR* argv[])
{
    CommandlineOptions commandlineOptions;
    int c(0);
    while ((c = getopt(argc, argv, L"n:pcb")) != EOF) {
        switch (c) {
        case L'n': commandlineOptions.printerName = optarg; break;
        case L'p': commandlineOptions.print = true; break;
        case L'c': commandlineOptions.cardCompletion = true; break;
        case L'b': commandlineOptions.parkBack = true; break;
        default: usage();
        }
    }

    if (commandlineOptions.printerName.IsEmpty()) {
        usage();
    }

    return commandlineOptions;
}

////////////////////////////////////////////////////////////////////////////////
// main()
////////////////////////////////////////////////////////////////////////////////
int wmain(int argc, WCHAR* argv[])
{
    const CommandlineOptions cmdLineOpts = GetCommandLineOptions(argc, argv);

    CComPtr <IBidiSpl> bidiSpl;
    int printerJobID(0);

    try {
        HRESULT hr = ::CoInitialize(nullptr);
        if (FAILED(hr)) {
            std::cerr << "::CoInitialize:" << " 0x" << std::hex << hr << " " << CT2A(util::Win32ErrorString(hr));
            return -1;
        }

        // create an IBidiSpl COM smartpointer instance, and bind it to the
        // printer name. We use it for all subsequent call using the IBidi interface.
        hr = bidiSpl.CoCreateInstance(CLSID_BidiSpl);
        if (FAILED(hr)) {
            std::cerr << "bidiSpl.CoCreateInstance:" << " 0x" << std::hex << hr << " " << CT2A(util::Win32ErrorString(hr));
            return -1;
        }

        hr = bidiSpl->BindDevice(cmdLineOpts.printerName, BIDI_ACCESS_USER);
        if (FAILED(hr)) {
            std::cerr << "BindDevice():" << " 0x" << std::hex << hr << " " << CT2A(cmdLineOpts.printerName) << " " << CT2A(util::Win32ErrorString(hr));
            return -1;
        }

        // optionally display driver version
        CString driverVersionXml = util::GetDriverVersionXML(bidiSpl);
        CString driverVersionString = util::ParseDriverVersionXML(driverVersionXml);
        std::cout << std::endl << "driver version: " << CW2A(driverVersionString) << std::endl << std::endl;

        CString printerOptionsXML = util::GetPrinterOptionsXML(bidiSpl);
        util::PrinterOptionsValues printerOptionsValues = util::ParsePrinterOptionsXML(printerOptionsXML);
        if ("Ready" != printerOptionsValues._printerStatus && "Busy" != printerOptionsValues._printerStatus) {
            std::cerr << "printer " << CT2A(cmdLineOpts.printerName) << " is not ready.";
            return -1;
        }

        // this application uses the single-wire method for communicating with
        // a smartcard reader in the printer. If the printer is not configured with
        // that option, we're done.
        if ("Single wire" != printerOptionsValues._optionSmartcard) {
            std::cerr << "printer '" << CT2A(cmdLineOpts.printerName) << "' does not have single-wire smartcard option. Exiting." << std::endl;;
            return -1;
        }

        CString hopperID;
        printerJobID = util::StartJob(
            bidiSpl,
            hopperID);

        const auto printerStatusValues = SmartcardCommon::ParkCard(bidiSpl, cmdLineOpts.parkBack);

        PersonalizeSmartcard(bidiSpl);

        SmartcardCommon::ResumeCard(printerJobID, bidiSpl);

        if (cmdLineOpts.print) {
            const bool doTwoSided =
                printerOptionsValues._optionDuplex == "Auto" ||
                printerOptionsValues._optionDuplex == "Manual";

            // Important: util::PrintText() waits until driver gets all the print data.
            util::PrintText(cmdLineOpts.printerName, doTwoSided);
        }

        util::EndJob(bidiSpl);

        if (cmdLineOpts.cardCompletion) {
            util::PollForJobCompletion(bidiSpl, printerJobID);
        }
    }
    catch (util::BidiException& e) {
        std::cerr << e.what() << std::endl;
        util::CancelJob(bidiSpl, e.printerJobID, e.errorCode);
    }
    catch (std::runtime_error& e) {
        std::cerr << e.what() << std::endl;
        if (0 != printerJobID) {
            util::CancelJob(bidiSpl, printerJobID, 0);
        }
    }

    if (bidiSpl) {
        bidiSpl->UnbindDevice();
        bidiSpl = nullptr;
    }

    ::CoUninitialize();
    std::cout << std::endl;
    system("pause");
    return 0;
}