////////////////////////////////////////////////////////////////////////////////
// Copyright (c) Datacard Corporation.  All Rights Reserved.
////////////////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include <iostream>
#include <sstream>
#include <vector>
#include "XGetopt.h"
#include "util.h"
#include "DXP01SDK.H"

using namespace std;

// Sigma supported colorModes
const vector<std::wstring> SigmaColormodeValues = {
    L"TrueColor (ICC)",
    L"Vivid",
    L"SD/CD",
    L"Custom"
};

// CR805 supported colorModes
const vector<std::wstring> CR805ColormodeValues = {
    L"TrueColor (ICC)",
    L"Vivid",
    L"Legacy SR/CR",
    L"Legacy SR wLUT",
    L"Adobe RGB"
};

enum Commands {
    undefined,
    cancelAllJobs,
    restart,
    resetCardCounts,
	adjustColors,
    defaultColors,
    cleanPrinter,
    cleanLaminator,
    powerDownPrinter,
    getColorMode,
    setColorMode,
    wakePrinter,
    sleepPrinter
};

struct CommandLineOptions {
    CommandLineOptions() : command(undefined)
    {}

    CString  printerName;
    Commands command;
    CString  colorMode{};
};

void usage()
{
    const string shortExeName = CT2A(util::GetShortExeName());
    cout << endl << shortExeName << " controls the printer through the printer driver." << endl << endl;
    cout << shortExeName << " -n <printername> <command>" << endl;
    cout << "  -n <printername>. Required. Try -n \"XPS Card Printer\"." << endl;
    cout << "  -c cancel all jobs: Send a command to the printer to clear all print jobs" << endl;
    cout << "     from the printer. This does not delete spooled Windows" << endl;
    cout << "     print jobs." << endl;
    cout << "  -p power down the printer." << endl;
    cout << "  -q clean the printer." << endl;
    cout << "  -r restart the printer." << endl;
    cout << "  -w wake the printer." << endl;
    cout << "  -x put the printer to sleep." << endl;
    cout << "  -e reset all card counts." << endl;
	cout << "  -a set colorAdjust settings.  This sample changes only the Red settings. Blue and Green are unchanged" << endl;
    cout << "  -d set default colorAdjust settings. This sample only resets Red and Green.  Blue is unchanged. " << endl;
    cout << "  -s set colorMode <colorMode>. " << endl;
    ::exit(-1);
}

void ValidateCommandLineOptions(CommandLineOptions& commandLineOptions)
{
    if (commandLineOptions.printerName.IsEmpty()) {
        usage();
    }
    if (undefined == commandLineOptions.command) {
        usage();
    }
}

CommandLineOptions GetCommandlineOptions(const int argc, _TCHAR* argv[])
{
    CommandLineOptions commandLineOptions;
    int c(0);
    while ((c = getopt(argc, argv, _T("n:cpqreadgwxs:"))) != EOF) {
        switch (c) {
        case L'n': commandLineOptions.printerName = optarg; break;
        case L'c': commandLineOptions.command = cancelAllJobs; break;
        case L'p': commandLineOptions.command = powerDownPrinter; break;
        case L'q': commandLineOptions.command = cleanPrinter; break;
        case L'r': commandLineOptions.command = restart; break;
        case L'e': commandLineOptions.command = resetCardCounts; break;
		case L'a': commandLineOptions.command = adjustColors; break;
        case L'd': commandLineOptions.command = defaultColors; break;
        case L's':
            commandLineOptions.command = setColorMode;
            commandLineOptions.colorMode = optarg;
            break;
        case L'w': commandLineOptions.command = wakePrinter; break;
        case L'x': commandLineOptions.command = sleepPrinter; break;
        default:
            usage();
        }
    }
    return commandLineOptions;
}

/////////////////////////////////////////////////////////////////////////
// RestartPrinter
/////////////////////////////////////////////////////////////////////////
void RestartPrinter(CComPtr <IBidiSpl>& bidiSpl)
{
    stringstream exceptionText;
    CComPtr <IBidiRequest> bidiRequest;
    HRESULT hr = bidiRequest.CoCreateInstance(CLSID_BidiRequest);
    if (FAILED(hr)) {
        exceptionText << "bidiRequest.CoCreateInstance(CLSID_BidiRequest):" << " 0x" << hex << hr << " " << CT2A(util::Win32ErrorString(hr));
        throw runtime_error(exceptionText.str());
    }

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

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

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

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

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

/////////////////////////////////////////////////////////////////////////
// PowerDownPrinter
/////////////////////////////////////////////////////////////////////////
void PowerDownPrinter(CComPtr <IBidiSpl>& bidiSpl)
{
    stringstream exceptionText;
    CComPtr <IBidiRequest> bidiRequest;
    HRESULT hr = bidiRequest.CoCreateInstance(CLSID_BidiRequest);
    if (FAILED(hr)) {
        exceptionText << "bidiRequest.CoCreateInstance(CLSID_BidiRequest):" << " 0x" << hex << hr << " " << CT2A(util::Win32ErrorString(hr));
        throw runtime_error(exceptionText.str());
    }

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

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

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

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

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

/////////////////////////////////////////////////////////////////////////
// WakePrinter
/////////////////////////////////////////////////////////////////////////
void WakePrinter(CComPtr <IBidiSpl>& bidiSpl)
{
    stringstream exceptionText;
    CComPtr <IBidiRequest> bidiRequest;
    HRESULT hr = bidiRequest.CoCreateInstance(CLSID_BidiRequest);
    if (FAILED(hr)) {
        exceptionText << "bidiRequest.CoCreateInstance(CLSID_BidiRequest):" << " 0x" << hex << hr << " " << CT2A(util::Win32ErrorString(hr));
        throw runtime_error(exceptionText.str());
    }

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

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

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

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

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

/////////////////////////////////////////////////////////////////////////
// SleepPrinter
/////////////////////////////////////////////////////////////////////////
void SleepPrinter(CComPtr <IBidiSpl>& bidiSpl)
{
    stringstream exceptionText;
    CComPtr <IBidiRequest> bidiRequest;
    HRESULT hr = bidiRequest.CoCreateInstance(CLSID_BidiRequest);
    if (FAILED(hr)) {
        exceptionText << "bidiRequest.CoCreateInstance(CLSID_BidiRequest):" << " 0x" << hex << hr << " " << CT2A(util::Win32ErrorString(hr));
        throw runtime_error(exceptionText.str());
    }

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

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

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

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

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



bool caseInsensitiveCharCompareW(wchar_t a, wchar_t b)
{
    return (toupper(a) == toupper(b));
}

bool caseInsensitiveCompareW(const wstring& s1, const wstring& s2)
{
    return ((s1.size() == s2.size()) &&
        equal(s1.begin(), s1.end(), s2.begin(), caseInsensitiveCharCompareW));
}



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

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

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

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

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

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

////////////////////////////////////////////////////////////////////////////////
// CancelAllJobs()
//
// using the IBidiSpl interface, issue a 'cancel all jobs' command. This is
// simply a dxp01sdk::Cancel with both printerJobID and errorCode set to zero.
//
////////////////////////////////////////////////////////////////////////////////
void CancelAllJobs(CComPtr <IBidiSpl>& bidiSpl)
{
    stringstream exceptionText;
    CComPtr <IBidiRequest> bidiRequest;
    HRESULT hr = bidiRequest.CoCreateInstance(CLSID_BidiRequest);
    if (FAILED(hr)) {
        exceptionText << "CoCreateInstance(CLSID_BidiRequest):" << " 0x" << hex << hr << " " << CT2A(util::Win32ErrorString(hr));
        throw runtime_error(exceptionText.str());
    }
    // create the XML and convert to bytes for the upcoming SetInputData() call.
    // use zero for jobID and errorCode; this will cancel all cards in printer.
    CString actionXML = util::FormatPrinterActionXML(
        dxp01sdk::Cancel,
        0,  // printerJobID
        0); // errorcode

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

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

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

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

    // Free the memory that was allocated for the IBidiSpl SendRecv call:
    ::CoTaskMemFree(xmlBytes);
}



//////////////////////////////////////////////////////////////////////////////////////////////
// GetPrinterMessages
/////////////////////////////////////////////////////////////////////////////////////////////
int GetPrinterMessages(CComPtr <IBidiSpl>& bidiSpl, CString& errorString)
{
    int errorCode = 0;

    // loop until error code is returned.
    // error code 172 = "insert cleaning card" is expected.
    // any other code is an error
    int count = 5;
    for (int i = 0; i < count; i++) {
        CString printerStatusXML = util::GetPrinterStatusXML(bidiSpl);
        util::PrinterStatusValues printerStatusValues = util::ParsePrinterStatusXML(printerStatusXML);

        errorCode = printerStatusValues._errorCode;
        errorString = printerStatusValues._errorString;

        if (errorCode != 0) {
            break;
        }

        if (i < count) {
            Sleep(1000);
        }
    }
    
    return errorCode;
}

//////////////////////////////////////////////////////////////////////////////////////////////
// ProcessCleaningCardErrorCode
/////////////////////////////////////////////////////////////////////////////////////////////
void ProcessCleaningCardErrorCode(CComPtr <IBidiSpl>& bidiSpl, int errorCode, CString& errorString)
{
    switch (errorCode) {
    case 0:
        // HMM - did not get error code
        cout << " - Did not get proper return code from driver" << endl;
        CancelAllJobs(bidiSpl);
        break;
    case 172:
        cout << " - Insert Cleaning card." << endl;
        break;
    default:
        cout << "CleanPrinter: " << " " << CT2A(errorString) << endl;
        CancelAllJobs(bidiSpl);
    }
}


//////////////////////////////////////////////////////////////////////////////////////////////
// Clean printer
/////////////////////////////////////////////////////////////////////////////////////////////
void CleanPrinter(CComPtr <IBidiSpl>& bidiSpl)
{
    stringstream exceptionText;

    CString printerVersionXml = util::GetPrinterOptionsXML(bidiSpl);
    util::PrinterOptionsValues printerOptionsValues = util::ParsePrinterOptionsXML(printerVersionXml);

    // verify that the printer is in the READY state
    wstring wstrPrinterState = printerOptionsValues._printerStatus.GetString();
    if (!(caseInsensitiveCompareW(wstrPrinterState, dxp01sdk::READY))) {
        exceptionText << "ERROR:  printer is not in state 'READY'.";
        throw runtime_error(exceptionText.str());
    }

    CComPtr <IBidiRequest> bidiRequest;

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

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

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

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

        if (FAILED(cleanResultCode)) {
            exceptionText << "cleanResultCode:" << " 0x" << hex << cleanResultCode << " " << util::Win32ErrorString(cleanResultCode).GetBuffer();
            throw runtime_error(exceptionText.str());
        }
    }
    catch (runtime_error& e) {
        cerr << e.what() << endl << endl;
    }

    CString errorString = "";

    int errorCode = GetPrinterMessages(bidiSpl, errorString);

    ProcessCleaningCardErrorCode(bidiSpl, errorCode, errorString);

    return;
}


//////////////////////////////////////////////////////////////////////////////////////////////
// CleanLaminator
/////////////////////////////////////////////////////////////////////////////////////////////
void CleanLaminator(CComPtr <IBidiSpl>& bidiSpl)
{
    stringstream exceptionText;

    CString printerVersionXml = util::GetPrinterOptionsXML(bidiSpl);
    util::PrinterOptionsValues printerOptionsValues = util::ParsePrinterOptionsXML(printerVersionXml);

    // verify that the printer is in the READY state
    wstring wstrPrinterState = printerOptionsValues._printerStatus.GetString();
    if (!(caseInsensitiveCompareW(wstrPrinterState, dxp01sdk::READY))) {
        exceptionText << "ERROR:  printer is not in state 'READY'.";
        throw runtime_error(exceptionText.str());
    }
    // verify that the printer has a laminator installed
    wstring wstrLaminatorPresent = printerOptionsValues._laminator.GetString();
    if (caseInsensitiveCompareW(wstrLaminatorPresent, dxp01sdk::NONE)) {
        exceptionText << "ERROR:  printer does not have a laminator installed.";
        throw runtime_error(exceptionText.str());
    }

    CComPtr <IBidiRequest> bidiRequest;

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

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

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

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

        if (FAILED(cleanResultCode)) {
            exceptionText << "cleanResultCode:" << " 0x" << hex << cleanResultCode << " " << util::Win32ErrorString(cleanResultCode).GetBuffer();
            throw runtime_error(exceptionText.str());
        }
    }
    catch (runtime_error& e) {
        cerr << e.what() << endl << endl;
    }

    CString errorString = "";

    int errorCode = GetPrinterMessages(bidiSpl, errorString);

    ProcessCleaningCardErrorCode(bidiSpl, errorCode, errorString);

    return;
}


////////////////////////////////////////////////////////////////////////////////
// AdjustColors()
//
// using the IBidiSpl interface, issue an 'AdjustColors' command.
// This sample changes only the Red settings. Blue and Green are unchanged.
//
////////////////////////////////////////////////////////////////////////////////

void AdjustColors(CComPtr <IBidiSpl>& bidiSpl)
{
    stringstream exceptionText;
    CComPtr <IBidiRequest> bidiRequest;

    HRESULT hr = bidiRequest.CoCreateInstance(CLSID_BidiRequest);
    if (FAILED(hr)) {
        exceptionText << "CoCreateInstance(CLSID_BidiRequest):" << " 0x" << hex << hr << " " << CT2A(util::Win32ErrorString(hr));
        throw runtime_error(exceptionText.str());
    }

    // create the XML and convert to bytes for the upcoming SetInputData() call.
    // Changing the settings for the Red color channel while leaving the other two colors unchanged:
    CString formattedColorForRChannel;
    formattedColorForRChannel.Format(
        dxp01sdk::COLOR_CHANNEL,
        L"1", L"2", L"-1", L"0", L"25", L"6", L"-19", L"12", L"13", L"14", L"15");

    CString adjustColorXML;
    adjustColorXML.Format(
        dxp01sdk::ADJUST_COLOR_XML,
        formattedColorForRChannel.GetString(), L"", L"");

    const int xmlBytesLength = adjustColorXML.GetLength() * sizeof WCHAR;

    // 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, adjustColorXML.GetBuffer(), xmlBytesLength);

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

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

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

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

    // Free the memory that was allocated for the IBidiSpl SendRecv call:
    ::CoTaskMemFree(xmlBytes);

}



////////////////////////////////////////////////////////////////////////////////
// SetDefaultColors()
//
// using the IBidiSpl interface, issue an 'SetDefaultColors' command.
// This sample only resets Red and Green.  Blue is unchanged.
//
////////////////////////////////////////////////////////////////////////////////
void SetDefaultColors(CComPtr <IBidiSpl>& bidiSpl)
{
    stringstream exceptionText;
    CComPtr <IBidiRequest> bidiRequest;
    HRESULT hr = bidiRequest.CoCreateInstance(CLSID_BidiRequest);
    if (FAILED(hr)) {
        exceptionText << "CoCreateInstance(CLSID_BidiRequest):" << " 0x" << hex << hr << " " << CT2A(util::Win32ErrorString(hr));
        throw runtime_error(exceptionText.str());
    }

    // create the XML and convert to bytes for the upcoming SetInputData() call.
    // Setting red and green color channel values back to defaults and keeping blue unchanged
    CString defaultColorXML;
    defaultColorXML.Format(
        dxp01sdk::DEFAULT_COLOR_XML,
        L"true", L"true", L"false");

    const int xmlBytesLength = defaultColorXML.GetLength() * sizeof WCHAR;

    // 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, defaultColorXML.GetBuffer(), xmlBytesLength);

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

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

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


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

    // Free the memory that was allocated for the IBidiSpl SendRecv call:
    ::CoTaskMemFree(xmlBytes);
}


////////////////////////////////////////////////////////////////////////////////
// isColorAdjustCapable()
//
// Returns true if printer is in READY state and firmware is >= D3.17.4
// otherwise returns false.
//
////////////////////////////////////////////////////////////////////////////////
bool isColorAdjustCapable(CComPtr <IBidiSpl> bidiSpl)
{
    stringstream exceptionText;

    CString printerVersionXml = util::GetPrinterOptionsXML(bidiSpl);
    util::PrinterOptionsValues printerOptionsValues = util::ParsePrinterOptionsXML(printerVersionXml);

    cout << endl << "printer state: " << CW2A(printerOptionsValues._printerStatus) << endl << endl;

    // verify that the printer is in the READY state
    if ("Ready" != printerOptionsValues._printerStatus ) {
        exceptionText << "ERROR:  printer is not in state 'READY'.";
        throw runtime_error(exceptionText.str());
    }

    cout << endl << "printer version: " << CW2A(printerOptionsValues._printerVersion) << endl << endl;
    util::FirmwareVersion actualFirmware = util::ParseFirmwareRev(printerOptionsValues._printerVersion);

    if ((actualFirmware._printerBase == "D3") && (actualFirmware._majorVersion > 16)) {
        if (actualFirmware._majorVersion > 17)
            return true;
        else if ((actualFirmware._majorVersion == 17) && (actualFirmware._minorVersion >= 4))
            return true;
    }
    else if (actualFirmware._printerBase == "D4") {
        return true;
    }

    exceptionText << "ERROR - Requires printer version: D3.17.4 (or greater)";
    throw runtime_error(exceptionText.str());
}


////////////////////////////////////////////////////////////////////////////////
// isColorModeCapable()
//
// Returns true if printer is in READY state and appropriate colormode has
// been provided
////////////////////////////////////////////////////////////////////////////////
bool IsColorModeCapable(CComPtr <IBidiSpl>& bidiSpl, std::wstring& colorMode) 
{
    CString printerVersionXml = util::GetPrinterOptionsXML(bidiSpl);
    util::PrinterOptionsValues printerOptionsValues = util::ParsePrinterOptionsXML(printerVersionXml);

    stringstream exceptionText;

    // verify that the printer is in the READY state
    wstring wstrPrinterState = printerOptionsValues._printerStatus.GetString();
    if (!(caseInsensitiveCompareW(wstrPrinterState, dxp01sdk::READY))) {
        exceptionText << "ERROR:  printer is not in state 'READY'.";
        throw runtime_error(exceptionText.str());
    }

    // verify that the user has passed in valid colormode value
    util::FirmwareVersion fwRev = util::ParseFirmwareRev(printerOptionsValues._printerVersion);

    bool bFound = false;
    if (fwRev._printerBase == L"R4") {  // CR805
        for (int i = 0; i < CR805ColormodeValues.size(); i++) {
            if (colorMode == CR805ColormodeValues[i]) {
                bFound = true;
                break;
            }
        }
        if (!bFound) {
            exceptionText << "ERROR: '" << (CW2A)colorMode.c_str() << "' is not a valid CR805 colormode.";
            throw runtime_error(exceptionText.str());
        }
    }
    else if (fwRev._printerBase == L"D4") {     // Sigma
        for (int i = 0; i < SigmaColormodeValues.size(); i++) {
            if (colorMode == SigmaColormodeValues[i]) {
                bFound = true;
                break;
            }
        }
        if (!bFound) {
            exceptionText << "ERROR: '" << (CW2A)colorMode.c_str() << "' is not a valid Sigma colormode.";
            throw runtime_error(exceptionText.str());
        }
    }
    else {
        exceptionText << "ERROR: invalid printer type.";
        throw runtime_error(exceptionText.str());
    }

    return true;
}


////////////////////////////////////////////////////////////////////////////////
// setColorMode()
//
// using the IBidiSpl interface, issue a 'setColorMode' command.
//
////////////////////////////////////////////////////////////////////////////////

void SetColorMode(CComPtr <IBidiSpl>& bidiSpl, CString& colorMode)
{
    std::wstring wstrColormode = colorMode.GetString();
    stringstream exceptionText;

    // this method throws an exception if a problem was incurred
     // no need to check return code
    IsColorModeCapable(bidiSpl, wstrColormode);

    CComPtr <IBidiRequest> bidiRequest;
    HRESULT hr = bidiRequest.CoCreateInstance(CLSID_BidiRequest);
    if (FAILED(hr)) {
        exceptionText << "CoCreateInstance(CLSID_BidiRequest):" << " 0x" << hex << hr << " " << CT2A(util::Win32ErrorString(hr));
        throw runtime_error(exceptionText.str());
    }

    CString submitSettingsXML;
    CString settingSubsystem(L"Print");
    CString settingName(L"ColorMode");
    wcout << "    settingSubsystem = " << settingSubsystem.GetString() << endl;
    wcout << "    settingName = " << settingName.GetString() << endl;
    wcout << "    value = " << colorMode.GetString() << endl;

    submitSettingsXML.Format(dxp01sdk::SUBMIT_SETTINGS_XML, settingSubsystem.GetString(), settingName.GetString(), colorMode.GetString());
    const int xmlBytesLength = submitSettingsXML.GetLength() * sizeof WCHAR;

    // 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, submitSettingsXML.GetBuffer(), xmlBytesLength);

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

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

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

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

    // Free the memory that was allocated for the IBidiSpl SendRecv call:
    ::CoTaskMemFree(xmlBytes);
}


////////////////////////////////////////////////////////////////////////////////
// main()
////////////////////////////////////////////////////////////////////////////////
int _tmain(int argc, _TCHAR* argv[])
{

    CommandLineOptions commandLineOptions = GetCommandlineOptions(argc, argv);

    ValidateCommandLineOptions(commandLineOptions);

    CComPtr <IBidiSpl> bidiSpl;
    try {
        stringstream exceptionText;

        HRESULT hr = ::CoInitialize(NULL);
        if (FAILED(hr)) {
            exceptionText << "::CoInitialize:" << " 0x" << hex << hr << " " << CT2A(util::Win32ErrorString(hr));
            throw 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" << hex << hr << " " << CT2A(util::Win32ErrorString(hr));
            throw runtime_error(exceptionText.str());
        }

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

        // There is a bug in SendRecv interface. Driver does not get the correct printer name
        // if user renames the printer. We mimic sending data to driver and that makes sure that
        // driver updates the printer name.
        util::RawDataToXpsPrinter(commandLineOptions.printerName.GetBuffer(), NULL, 0);

        switch (commandLineOptions.command) {
        case cancelAllJobs:
            CancelAllJobs(bidiSpl);
            cout << "all jobs canceled for '" << CT2A(commandLineOptions.printerName) << "'" << endl;
            break;
        case cleanPrinter:
            cout << "command issued to clean the printer." << endl;
            CleanPrinter(bidiSpl);
            break;
        case cleanLaminator:
            cout << "command issued to clean the laminator." << endl;
            CleanLaminator(bidiSpl);
            break;
        case powerDownPrinter:
            PowerDownPrinter(bidiSpl);
            cout << "command issued to power down the printer." << endl;
            break;
        case restart:
            RestartPrinter(bidiSpl);
            cout << "command issued to restart the printer." << endl;
			break;
        case wakePrinter:
            WakePrinter(bidiSpl);
            cout << "command issued to wake the printer." << endl;
            break;
        case sleepPrinter:
            SleepPrinter(bidiSpl);
            cout << "command issued to put the printer to sleep." << endl;
            break;
        case resetCardCounts:
            ResetCardCounts(bidiSpl);
            cout << "command issued to reset the printer's card counts." << endl;
            break;
		case adjustColors:
            cout << "AdjustColors - begin." << endl;
            if (isColorAdjustCapable(bidiSpl)) {
                AdjustColors(bidiSpl);
            }
            cout << "AdjustColors - end." << endl;
            break;
		case defaultColors:
            cout << "SetDefaultColors - begin." << endl;           
            if (isColorAdjustCapable(bidiSpl)) {
                SetDefaultColors(bidiSpl);
            }
            cout << "SetDefaultColors - end." << endl;
            break;
        case setColorMode:
            cout << "SetColorMode - begin." << endl;
            SetColorMode(bidiSpl, commandLineOptions.colorMode);
            cout << "SetColorMode - end." << endl;
            break;
        default:
            throw runtime_error("unexpected commandline option");
        }
    }
    catch (runtime_error& e) {
        cerr << e.what() << endl << endl;
    }

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

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