////////////////////////////////////////////////////////////////////////////////
// Copyright (c) Datacard Corporation.  All Rights Reserved.
//
// common methods are used by the the "smartcard" sdk samples
////////////////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include <iostream>
#include <sstream>
#include <iomanip>
#include "SmartcardCommon.h"



	
void SmartcardCommon::DisplayProtocols(const UINT protocol)
{
    auto protocolStrings = dxp01sdk::SCard::StringsFromProtocol(protocol);
    std::vector <std::wstring>::iterator iter;
    std::cout << "   protocol[s]:" << std::endl;
    for (iter = protocolStrings.begin(); iter < protocolStrings.end(); iter++) {
        std::wcout << "  " << iter->c_str();
    }
    std::cout << std::endl;
}

void SmartcardCommon::DisplayByteVectorAsHex(std::string title, std::vector <byte> bytes)
{
    std::cout << title << ":" << std::endl;
    for (size_t index = 0; index < bytes.size(); index++) {
        std::cout << std::setw(2) << std::setfill('0') << std::uppercase << std::hex << int(bytes[index]) << " ";
    }
    std::cout << std::dec << std::endl;
}

util::PrinterStatusValues SmartcardCommon::ParkCard(CComPtr <IBidiSpl>& bidiSpl, const bool parkBack)
{
    std::stringstream exceptionText;
    CComPtr <IBidiRequest> bidiRequest;
    HRESULT hr = bidiRequest.CoCreateInstance(CLSID_BidiRequest);
    if (FAILED(hr)) {
        exceptionText << "bidiRequest.CoCreateInstance(CLSID_BidiRequest):" << " 0x" << std::hex << hr << " " << CT2A(util::Win32ErrorString(hr));
        throw std::runtime_error(exceptionText.str());
    }

    const auto parkCommand = parkBack ? dxp01sdk::SMARTCARD_PARK_BACK : dxp01sdk::SMARTCARD_PARK;
    hr = bidiRequest->SetSchema(parkCommand);
    if (FAILED(hr)) {
        exceptionText << "SetSchema(dxp01sdk::SMARTCARD_PARK)):" << " 0x" << std::hex << hr << " " << util::Win32ErrorString(hr).GetBuffer();
        throw std::runtime_error(exceptionText.str());
    }

    hr = bidiSpl->SendRecv(BIDI_ACTION_SET, bidiRequest);
    if (FAILED(hr)) {
        exceptionText << "SendRecv(BIDI_ACTION_SET) park:" << " 0x" << std::hex << hr << " " << util::Win32ErrorString(hr).GetBuffer();
        throw std::runtime_error(exceptionText.str());
    }

    HRESULT parkCardResultCode(S_OK);
    hr = bidiRequest->GetResult(&parkCardResultCode);
    if (FAILED(hr)) {
        exceptionText << "GetResult(park):" << " 0x" << std::hex << hr << " " << util::Win32ErrorString(hr).GetBuffer();
        throw std::runtime_error(exceptionText.str());
    }

    if (FAILED(parkCardResultCode)) {
        exceptionText << "parkCardResultCode:" << " 0x" << std::hex << parkCardResultCode << " " << util::Win32ErrorString(parkCardResultCode).GetBuffer();
        throw std::runtime_error(exceptionText.str());
    }

    CString printerStatusXML = util::GetPrinterStatusXML(bidiRequest);
    util::PrinterStatusValues printerStatusValues = util::ParsePrinterStatusXML(printerStatusXML);
    if (0 != printerStatusValues._errorCode) {
        exceptionText << "ParkCard(): " << printerStatusValues._errorCode << " " << CT2A(printerStatusValues._errorString);
        throw util::BidiException(exceptionText.str(), printerStatusValues._printerJobID, printerStatusValues._errorCode);
    }

    return printerStatusValues;
}


void SmartcardCommon::ResumeCard(const int printerJobID, CComPtr <IBidiSpl>& bidiSpl)
{
    std::stringstream exceptionText;
    CComPtr <IBidiRequest> bidiRequest;
    HRESULT hr = bidiRequest.CoCreateInstance(CLSID_BidiRequest);
    if (FAILED(hr)) {
        exceptionText << "bidiRequest.CoCreateInstance(CLSID_BidiRequest):" << " 0x" << std::hex << hr << " " << CT2A(util::Win32ErrorString(hr));
        throw std::runtime_error(exceptionText.str());
    }

    // create the XML and convert to bytes for the upcoming SetInputData() call:
    CString actionXML = util::FormatPrinterActionXML(dxp01sdk::Resume, printerJobID, 0);

    const int xmlBytesLength = actionXML.GetLength() * sizeof WCHAR; // UNICODE

    // IBidiSpl compatible memory:
    CComAllocator XMLBytesAllocator;
    PBYTE xmlBytes = (PBYTE) XMLBytesAllocator.Allocate(xmlBytesLength);
    DBG_UNREFERENCED_LOCAL_VARIABLE(XMLBytesAllocator); // suppress compiler warning

    // copy the XML string...without the trailing NULL:
    ::memcpy_s(xmlBytes, xmlBytesLength, actionXML.GetBuffer(), xmlBytesLength);

    hr = bidiRequest->SetSchema(dxp01sdk::PRINTER_ACTION);
    if (FAILED(hr)) {
        exceptionText << "SetSchema(dxp01sdk::XPS_CARDPRINTER_SCHEMA_PRINTER_ACTION):" << " 0x" << std::hex << hr << " " << util::Win32ErrorString(hr).GetBuffer();
        throw std::runtime_error(exceptionText.str());
    }

    hr = bidiRequest->SetInputData(BIDI_BLOB, xmlBytes, xmlBytesLength);
    if (FAILED(hr)) {
        exceptionText << "bidiRequest->SetInputData():" << " 0x" << std::hex << hr << " " << util::Win32ErrorString(hr).GetBuffer();
        throw std::runtime_error(exceptionText.str());
    }

    hr = bidiSpl->SendRecv(BIDI_ACTION_SET, bidiRequest);
    if (FAILED(hr)) {
        exceptionText << "SendRecv(BIDI_ACTION_SET) ResumeCard:" << " 0x" << std::hex << hr << " " << util::Win32ErrorString(hr).GetBuffer();
        throw std::runtime_error(exceptionText.str());
    }
}



////////////////////////////////////////////////////////////////////////////////
// SCardResultMessage()
// format a nice message for display.
// SCARD errors are declared in WinError.H
////////////////////////////////////////////////////////////////////////////////
std::string SmartcardCommon::SCardResultMessage(const long scardResult, const std::string message)
{
    std::stringstream exceptionText;
    exceptionText << message
        << " result: "
        << scardResult
        << "; "
        << CT2A(util::Win32ErrorString(scardResult));
    return exceptionText.str();
}


////////////////////////////////////////////////////////////////////////////////
// GetSCardStatus()
//
// issue the SCardStatus() call. this is important, because the SCardConnect()
// call always succeeds - as long as the card is staged.
//
// the SCardConnect() call returns the Answer-To-Reset bytes (ATR).
////////////////////////////////////////////////////////////////////////////////
void SmartcardCommon::GetSCardStatus(dxp01sdk::SCard& scard)
{

    std::cout << std::endl;
    std::cout << "GetSCardStatus()" << std::endl;

    std::stringstream exceptionText;
    std::vector <DWORD> states;
    std::vector <byte> ATRBytes;
    DWORD protocol(SCARD_PROTOCOL_UNDEFINED);

    long scardResult = scard.SCardStatus(states, protocol, ATRBytes);
    if (scardResult != SCARD_S_SUCCESS) {
        exceptionText << SCardResultMessage(scardResult, "SCardStatus()");
        throw std::runtime_error(exceptionText.str());
    }

    std::cout << "SCardStatus() states: ";
    for (size_t index = 0; index < states.size(); index++) {
        std::wcout << states[index] << " " << dxp01sdk::SCard::StringFromState(states[index]) << " ";
    } std::cout << std::endl;

    const std::vector <DWORD>::iterator cardAbsent = find(states.begin(), states.end(), (DWORD) SCARD_ABSENT);
    if (cardAbsent != states.end()) {
        // one of the states is SCARD_ABSENT; we're done.
        throw std::runtime_error("one of the states is SCARD_ABSENT.");
    }

    DisplayByteVectorAsHex("SCardStatus() ATRBytes (std::hex)", ATRBytes);
}


////////////////////////////////////////////////////////////////////////////////
// ReadVendorInfo()
//
////////////////////////////////////////////////////////////////////////////////
bool SmartcardCommon::ReadVendorInfo(dxp01sdk::SCard& scard)
{
    long scardResult = SCARD_S_SUCCESS;

    // try all these 'get attribute' items:
    DWORD attributesToTryArray[] = {
        SCARD_ATTR_VENDOR_NAME,
        SCARD_ATTR_VENDOR_IFD_SERIAL_NO,
        SCARD_ATTR_VENDOR_IFD_TYPE,     // this attribute is not returned successfully
        SCARD_ATTR_VENDOR_IFD_VERSION
    };
    std::vector <DWORD> attributesToTry(attributesToTryArray, attributesToTryArray + ARRAYSIZE(attributesToTryArray));

    for (size_t attribIndex = 0; attribIndex < attributesToTry.size(); attribIndex++) {

        std::cout << std::endl;
        std::vector <byte> scardAttributeBytes;

        scardResult = scard.SCardGetAttrib(attributesToTry[attribIndex], scardAttributeBytes);
        if (SCARD_S_SUCCESS != scardResult) {
            continue;
        }

        CString msg;
        msg.Format(L"SCardGetAttrib(%ls)", dxp01sdk::SCard::StringFromAttr(attributesToTry[attribIndex]).c_str());
        std::cout << SCardResultMessage(scardResult, std::string(CT2A(msg))) << std::endl;

        switch (attributesToTry[attribIndex]) {
        case SCARD_ATTR_VENDOR_IFD_VERSION:
        {
            // vendor-supplied interface device version: DWORD in the form 0xMMmmbbbb where
            //      MM = major version;
            //      mm = minor version; and
            //      bbbb = build number:
            const size_t byteCount = scardAttributeBytes.size();
            const short minorVersion = byteCount > 0 ? scardAttributeBytes[0] : 0;
            const short majorVersion = byteCount > 1 ? scardAttributeBytes[1] : 0;
            short buildNumber = 0;
            if (byteCount > 3) {
                buildNumber = (scardAttributeBytes[2] << 8) + scardAttributeBytes[3];
            }
            std::cout << "  SCARD_ATTR_VENDOR_IFD_VERSION:" << std::endl;
            std::cout << "  major: " << majorVersion << "  minor: " << minorVersion << "  build: " << buildNumber << std::endl;
        }
        break;

        default:
            scardAttributeBytes.push_back(0); // null-terminate this std::string for display.
            std::string stringFromBytes = (char *) &scardAttributeBytes[0];
            std::cout << " " << stringFromBytes << std::endl;
        }
    }

    return true;
}
