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

// Escapes are top to area; left to area; width; height : card in landscape orientation
const CString printBlockingEscape = "~PB%16.51 7.34 14.99 14.99?";
const CString topcoatRemovalEscape = "~TR%16.51 7.34 14.99 14.99?";

enum CardType {
    contact, contactless, undetermined
};

void usage()
{
    const auto msgFormat = LR"(
%s demonstrates smartcard personalization using the
single-wire embedded reader. Once staged, the app connects to a contact
and then a contactless chip. For each, an ATR is issued and some data bytes
are sent and received.

Optionally prints, encodes a magstripe, and waits for job completion.

Uses hardcoded data for printing.

%s -n <printername> [-p] [-c] [-b] [-f <side>] [-i <input hopper>]

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.
  -c Poll for job completion.
  -b use back of card for smartcard chip.
  -f <Front | Back>. Specify the front or back side the card will eject from.
  -i <input hopper>. Defaults to input hopper #1
)";

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




////////////////////////////////////////////////////////////////////////////////
// PersonalizeChip()
//
// talk to the chip (contact or contactless). return TRUE if we were able to
// connect to the chip at all.
////////////////////////////////////////////////////////////////////////////////
bool PersonalizeChip(dxp01sdk::SCard& scard)
{
    std::vector <DWORD> states;
    std::vector <byte> ATRBytes;

    SmartcardCommon::GetSCardStatus(scard);  // reads ATR
    SmartcardCommon::ReadVendorInfo(scard);

    // create a byte std::vector for the upcoming SCardTransmit() method.
    // these particular bytes should function with this type of contact chip:
    //
    //      MPCOS-EMV 16k
    //      GEMPLUS
    //      Datacard part number 806062-002
    //
    byte bytes[] = {0x00, 0xA4, 0x00, 0x00};
    std::vector <byte> sendBytes(bytes, bytes + ARRAYSIZE(bytes));

    std::cout << std::endl;
    SmartcardCommon::DisplayByteVectorAsHex("sending bytes (hex): ", sendBytes);


    // for those bytes, we should receive 0x61, 0x12
    std::vector <byte> receivedBytes;

    long scardResult = scard.SCardTransmit(sendBytes, receivedBytes);
    std::cout << SmartcardCommon::SCardResultMessage(scardResult, "SCardTransmit()") << std::endl;
    if (SCARD_S_SUCCESS == scardResult && receivedBytes.size()) {
        SmartcardCommon::DisplayByteVectorAsHex("bytes received (hex): ", receivedBytes);
    }

    receivedBytes.clear();
    sendBytes = {0x00, 0xC0, 0x00, 0x00, 0x12};

    std::cout << std::endl;
    SmartcardCommon::DisplayByteVectorAsHex("sending bytes (hex): ", sendBytes);

    // for those bytes, we should receive
    //  0x85, 0x10, 0x80, 0x01, 0x3F, 0x00, 0x38, 0x00, 0x00, 0x00,
    //  0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x6B, 0x90, 0x00

    scardResult = scard.SCardTransmit(sendBytes, receivedBytes);
    std::cout << SmartcardCommon::SCardResultMessage(scardResult, "SCardTransmit()") << std::endl;
    if (SCARD_S_SUCCESS == scardResult && receivedBytes.size()) {
        SmartcardCommon::DisplayByteVectorAsHex("bytes received (hex): ", receivedBytes);
    }

    return true;
}

////////////////////////////////////////////////////////////////////////////////
// PersonalizeSmartcard()
// smartcard personalization using Win32 / WinSCard.H styled wrapper class.
////////////////////////////////////////////////////////////////////////////////
CardType PersonalizeSmartcard(CComPtr <IBidiSpl>& bidiSpl)
{
    bool contactPersonalized(false);
    bool contactlessPersonalized(false);
    CardType cardType(undetermined);

    // create an instance of the wrapper for the Win32 SCard...() functions:
    dxp01sdk::SCard scard(bidiSpl);
    std::stringstream exceptionText;

    // try a contacted chip:
    DWORD protocol(SCARD_PROTOCOL_UNDEFINED);


    long scardResult = scard.SCardConnect(dxp01sdk::SCard::contact, protocol);
    std::cout << SmartcardCommon::SCardResultMessage(scardResult, "SCardConnect(contact)") << std::endl;

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

    cardType = contact;
    SmartcardCommon::DisplayProtocols(protocol);
    contactPersonalized = PersonalizeChip(scard);

    scardResult = scard.SCardDisConnect(SCARD_LEAVE_CARD);
    std::cout << SmartcardCommon::SCardResultMessage(scardResult, "SCardDisConnect()") << std::endl << std::endl;

    //
    // try a contactless chip:
    protocol = SCARD_PROTOCOL_UNDEFINED;

    scardResult = scard.SCardConnect(dxp01sdk::SCard::contactless, protocol);
    std::cout << SmartcardCommon::SCardResultMessage(scardResult, "SCardConnect(contactless)") << std::endl;

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

    cardType = contactless;
    SmartcardCommon::DisplayProtocols(protocol);
    contactlessPersonalized = PersonalizeChip(scard);

    scardResult = scard.SCardDisConnect(SCARD_LEAVE_CARD);
    std::cout << SmartcardCommon::SCardResultMessage(scardResult, "SCardDisConnect()") << std::endl << std::endl;

    if (!contactPersonalized && !contactlessPersonalized) {
        exceptionText << "neither contact nor contactless chip personalization succeeded. canceling job.";
        throw std::runtime_error(exceptionText.str());
    }
    return cardType;
}

void ValidateHopperID(CString hopperID)
{
    if (hopperID != L"") {
        if (
            hopperID != L"1" &&
            hopperID != L"2" &&
            hopperID != L"3" &&
            hopperID != L"4" &&
            hopperID != L"5" &&
            hopperID != L"6" &&
            hopperID != L"exception") {
            std::cout << "invalid hopperID: " << CW2A(hopperID) << std::endl;
            ::exit(-1);
        }
    }
}

void ValidateCardEjectSide(CString cardEjectSide)
{
    if (cardEjectSide != L"") {
        if (
            cardEjectSide != L"front" &&
            cardEjectSide != L"back") {
            std::cout << "invalid cardEjectSide: " << CW2A(cardEjectSide) << std::endl;
            ::exit(-1);
        }
    }
}


////////////////////////////////////////////////////////////////////////////////
// main()
////////////////////////////////////////////////////////////////////////////////
int wmain(int argc, WCHAR* argv[])
{
    CString printerName;
    bool print(false);
    bool jobCompletion(false);
    bool parkBack(false);
    CString commandLineHopperID = L"";
    CString commandLineCardEjectSide = L"";

    int c(0);
    while ((c = getopt(argc, argv, L"n:pcbf:i:")) != EOF) {
        switch (c) {
        case L'n': printerName = optarg; break;
        case L'p': print = true; break;
        case L'b': parkBack = true; break;
        case L'c': jobCompletion = true; break;
        case L'i':
            commandLineHopperID = CString(optarg).MakeLower();
            ValidateHopperID(commandLineHopperID);
            break;
        case L'f': commandLineCardEjectSide = CString(optarg).MakeLower();
            ValidateCardEjectSide(commandLineCardEjectSide);
            break;
        default: usage();
        }
    }

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

    CComPtr <IBidiSpl> bidiSpl;
    std::stringstream exceptionText;
    int printerJobID(0);

    try {
        HRESULT hr = ::CoInitialize(nullptr);
        if (FAILED(hr)) {
            exceptionText << "::CoInitialize:" << " 0x" << std::hex << hr << " " << CT2A(util::Win32ErrorString(hr));
            throw std::runtime_error(exceptionText.str());
        }

        // 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)) {
            exceptionText << "bidiSpl.CoCreateInstance:" << " 0x" << std::hex << hr << " " << CT2A(util::Win32ErrorString(hr));
            throw std::runtime_error(exceptionText.str());
        }

        hr = bidiSpl->BindDevice(printerName, BIDI_ACCESS_USER);
        if (FAILED(hr)) {
            exceptionText << "BindDevice():" << " 0x" << std::hex << hr << " " << CT2A(printerName) << " " << CT2A(util::Win32ErrorString(hr));
            throw std::runtime_error(exceptionText.str());
        }

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

        // see if the printer is in the Printer_Ready state:
        CString printerOptionsXML = util::GetPrinterOptionsXML(bidiSpl);
        util::PrinterOptionsValues printerOptionsValues = util::ParsePrinterOptionsXML(printerOptionsXML);
        if ("Ready" != printerOptionsValues._printerStatus && "Busy" != printerOptionsValues._printerStatus) {
            exceptionText << "printer " << CT2A(printerName) << " is not ready.";
            throw std::runtime_error(exceptionText.str());
        }

        // 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) {
            exceptionText << "printer '" << CT2A(printerName) << "' needs 'Single Wire' for smartcard option. ";
            exceptionText << "'" << CT2A(printerOptionsValues._optionSmartcard) << "' was returned." << std::endl;
            throw std::runtime_error(exceptionText.str());
        }

        if (print && ("Installed" != printerOptionsValues._printHead) && ("Version1" != printerOptionsValues._connectionProtocol)) {
			exceptionText << "printer " << CT2A(printerName) << " does not have a print head installed.";
			throw std::runtime_error(exceptionText.str());
        }

        CString hopperID = util::GetInputHopper(printerOptionsValues._optionInputHopper, commandLineHopperID);;
        CString cardEjectSide = L"Default";

        printerJobID = util::StartJob(
            bidiSpl,
            hopperID,
            commandLineCardEjectSide.GetLength() > 0 ? commandLineCardEjectSide : cardEjectSide);

        // park the card in the smartcard reader:
        util::PrinterStatusValues printerStatusValues = SmartcardCommon::ParkCard(bidiSpl, parkBack);

        CardType cardType = PersonalizeSmartcard(bidiSpl);

        SmartcardCommon::ResumeCard(printerJobID, bidiSpl);

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

            // Important: util::PrintText method also waits until driver gets all the print data.
            if (cardType == contact) {
                util::Escapes   escapes = {printBlockingEscape, topcoatRemovalEscape, parkBack ? util::escapeOnCardBack : util::escapeOnCardFront};
                util::PrintText(printerName, doTwoSided, escapes);
            }
            else {
                util::PrintText(printerName, doTwoSided);
            }
        }

        // notify the printer that all the data has been sent for the card:
        util::EndJob(bidiSpl);

        if (jobCompletion) {
            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();

    return 0;
}