////////////////////////////////////////////////////////////////////////////////
// XPS Driver SDK : smartcard_singlewire + mifare chip example
////////////////////////////////////////////////////////////////////////////////
using System;
using System.IO;
using System.Text;
using dxp01sdk;
using dxp01sdk.mifare;
using SmartcardCommonNamespace;
public class CommandLineOptions {
    public string printerName;
    public bool print;
    public bool jobCompletion;
    public bool parkBack;
};

internal class smartcard_singlewire_duali_mifare {

    private static void Usage() {
        var thisExeName = Path.GetFileName(Environment.GetCommandLineArgs()[0]);
        Console.Write(thisExeName);
        Console.WriteLine(" demonstrates interactive mode parking a");
        Console.WriteLine("card in the smart card station, performing single-wire smartcard functions,");
        Console.WriteLine("moving the card from the station, and options to print and poll for job");
        Console.WriteLine("completion.");
        Console.WriteLine();
        Console.WriteLine("Uses hardcoded data for printing.");
        Console.WriteLine();
        Console.WriteLine(thisExeName + " -n <printername> [-p] [-b] [-c]");
        Console.WriteLine();
        Console.WriteLine("options:");
        Console.WriteLine("  -n <printername>. Required. Try -n \"XPS Card Printer\".");
        Console.WriteLine("  -p Print text and graphic on front side of card.");
        Console.WriteLine("  -b use back of card for smartcard chip.");
        Console.WriteLine("  -c Poll for job completion.");
        Console.WriteLine();
        Console.WriteLine(thisExeName + " -n \"XPS Card Printer\"");
        Console.WriteLine("  Parks a card in the printer smart card station, connects to the MiFare chip");
        Console.WriteLine("  and performs some MiFare chip activities.");
        Console.WriteLine();
        Console.WriteLine(thisExeName + " -n \"XPS Card Printer\" -p -c");
        Console.WriteLine("  Parks a card in printer smart card station, moves the card from the station,");
        Console.WriteLine("  prints simple text and graphics on the front side of the card, and polls and");
        Console.WriteLine("  displays job status.");
        Console.WriteLine();
        Environment.Exit(-1);
    }

    private static CommandLineOptions GetCommandlineOptions(string[] args) {
        if (args.Length == 0) Usage();

        var commandLineOptions = new CommandLineOptions();
        var arguments = new CommandLine.Utility.Arguments(args);

        if (string.IsNullOrEmpty(arguments["n"])) {
            Console.WriteLine("printer name is required");
            Environment.Exit(-1);
        }
        else {
            // we might have a -n with no printer name:
            bool boolVal = false;
            if (Boolean.TryParse(arguments["n"], out boolVal)) {
                Console.WriteLine("printer name is required");
                Environment.Exit(-1);
            }
            commandLineOptions.printerName = arguments["n"];
        }

        commandLineOptions.print = !string.IsNullOrEmpty(arguments["p"]);
        commandLineOptions.jobCompletion = !string.IsNullOrEmpty(arguments["c"]);
        commandLineOptions.parkBack = !string.IsNullOrEmpty(arguments["b"]);

        return commandLineOptions;
    }


    ////////////////////////////////////////////////////////////////////////////
    /// <summary>
    /// get mifare status. see pg. 23, "CCID_Protocol_spec_120224_.pdf"
    /// </summary>
    ////////////////////////////////////////////////////////////////////////////
    private static void DisplayChipInfo(SCard scard) {
        var command = new Mifare_Command();
        var sendBytes = command.CreateGetCardStatusCommand();

        SmartcardCommon.DisplayByteVectorAsHex("DisplayChipInfo() - bytes sent", sendBytes);

        byte[] receivedBytes = new byte[0];

        var scardResult = scard.SCardTransmit(sendBytes, ref receivedBytes);
        Console.WriteLine( SmartcardCommon.SCardResultMessage(scardResult, "SCardTransmit()"));

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

            Console.WriteLine(Mifare_Common.StatusCodeBytesToString(receivedBytes));
            Mifare_Common.CheckStatusCode(receivedBytes[0]);
        }
    }

    /// <summary>
    /// load mifare keys
    /// </summary>
    private static void LoadKeys(
        SCard scard,
        byte sector,
        Mifare_Command.KeyType keyType) {

        Console.WriteLine();

        var command = new Mifare_Command();

        var sendBytes = command.CreateLoadKeysCommand(
            keyType,
            sector,
            Mifare_Common.mifare_test_key);

        SmartcardCommon.DisplayByteVectorAsHex("LoadKeys() - bytes sent", sendBytes);

        var receivedBytes = new byte[0];
        var scardResult = scard.SCardTransmit(sendBytes, ref receivedBytes);
        Console.WriteLine(SmartcardCommon.SCardResultMessage(scardResult, "SCardTransmit()"));
        if (scard_error.SCARD_S_SUCCESS == (scard_error) scardResult && receivedBytes.Length > 0) {
            SmartcardCommon.DisplayByteVectorAsHex("LoadKeys() response", receivedBytes);

            Console.WriteLine(Mifare_Common.StatusCodeBytesToString(receivedBytes));
            Mifare_Common.CheckStatusCode(receivedBytes[0]);
        }
        else {
            throw new Exception("prep for write: load keys fail");
        }
    }

    /// <summary>
    /// generate some sample data. here, just get a data/timesamp.
    /// </summary>
    /// <returns></returns>
    private static string create_test_data_string() {
        var testDataString = System.DateTime.Now.ToString("yyyy.mm.dd.hh.mm");
        return testDataString;
    }

    /// <summary>
    /// write some sample data to the given block and sector.
    /// </summary>
    private static void WriteData(
        SCard scard,
        byte sector,
        byte block,
        Mifare_Command.KeyType keyType,
        byte[] testDataBytes) {
        var command = new Mifare_Command();
        var sendBytes = command.CreateWriteBlockCommand(
            keyType,
            sector,
            block,
            testDataBytes);

        SmartcardCommon.DisplayByteVectorAsHex("WriteData() - bytes sent", sendBytes);

        var receivedBytes = new byte[0];
        var scardResult = scard.SCardTransmit(sendBytes, ref receivedBytes);
        if (scard_error.SCARD_S_SUCCESS == (scard_error) scardResult && receivedBytes.Length > 0) {
            SmartcardCommon.DisplayByteVectorAsHex("Write Block response", receivedBytes);
            Console.WriteLine(Mifare_Common.StatusCodeBytesToString(receivedBytes));
            Mifare_Common.CheckStatusCode(receivedBytes[0]);
        }
        else {
            throw new Exception("write data failed.");
        }
    }

    /// <summary>
    /// read some data from the given block and sector.
    /// </summary>
    private static void ReadData(
        SCard scard,
        byte sector,
        byte block,
        Mifare_Command.KeyType keyType) {
        // in this app, we've already performed a LoadKeys().

        Console.WriteLine();

        var command = new Mifare_Command();
        var sendBytes = command.CreateReadBlockCommand(keyType, sector, block);

        SmartcardCommon.DisplayByteVectorAsHex("ReadData() - bytes sent", sendBytes);

        var receivedBytes = new byte[0];
        var scardResult = scard.SCardTransmit(sendBytes, ref receivedBytes);
        if (scard_error.SCARD_S_SUCCESS == (scard_error) scardResult && receivedBytes.Length > 0) {
            Console.WriteLine(string.Format("read sector {0}, block {1} status:", sector, block));
            var apduResponse = new MiFare_Response(receivedBytes);
            if (apduResponse.BlockHasNonzeroData()) {
                Console.Write("  {0} commandBytes received: ", receivedBytes.Length);

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

                Console.WriteLine(Mifare_Common.StatusCodeBytesToString(receivedBytes));
                Mifare_Common.CheckStatusCode(receivedBytes[0]);
            }
            else {
                Console.WriteLine("sector is all zeros");
            }
        }
        else {
            throw new Exception(string.Format("read sector {0}, block {1} fail", sector, block));
        }

        var readDataString = Encoding.ASCII.GetString(receivedBytes, 1, 16);
        Console.WriteLine(string.Format("data read from sector {0}, block {1}: '{2}'", sector, block, readDataString));
    }

    ////////////////////////////////////////////////////////////////////////////////
    // SmartCardControl()
    ////////////////////////////////////////////////////////////////////////////////
    private static void SmartCardControl(BidiSplWrap bidiSpl)
    {

        var exceptionText = new StringBuilder();
        var scard = new SCard(bidiSpl);

        Console.WriteLine();

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

        SmartcardCommon.DisplayByteVectorAsHex("SCardControl bytes sent", controlCode);

        var receivedBytes = new byte[0];
        long scardResult = scard.SCardControl(controlCode, ref receivedBytes);
        Console.WriteLine(SmartcardCommon.SCardResultMessage(scardResult, "SCardControl()"));
        if (scard_error.SCARD_S_SUCCESS != (scard_error)scardResult)
        {
            var msg = SmartcardCommon.SCardResultMessage(scardResult, "SCardControl()");
            throw new Exception("SCardControl() fail: " + msg);
        }

        if (0 == receivedBytes.Length)
        {
            Console.WriteLine("no response from SCardControl command");
        }
        else
        {
            SmartcardCommon.DisplayByteVectorAsHex("SCardControl() response", receivedBytes);
        }
    }


    /// <summary>
    /// Demonstrate the single-wire smartcard calls. The wrapper class we consume
    /// is similar to the Win32 ::Scard...() functions.
    /// <returns>
    /// throw an Exception if any error.
    /// </returns>
    /// </summary>
    private static void PersonalizeSmartcard(BidiSplWrap bidiSpl) {
        var scard = new SCard(bidiSpl);
        var protocol = (uint) scard_protocol.SCARD_PROTOCOL_DEFAULT;

        var scardResult = scard.SCardConnect(SCard.ChipConnection.contactless, ref protocol);

        Console.WriteLine("SCardConnect() result: {0}", scardResult);

        if (scard_error.SCARD_S_SUCCESS != (scard_error) scardResult) {
            var msg = SmartcardCommon.SCardResultMessage(scardResult, "");
            throw new Exception("SCardConnect() fail: " + msg);
        }

        SmartcardCommon.DisplayProtocols(protocol); // optional

        SmartcardCommon.GetSCardStatus(scard);

        DisplayChipInfo(scard);

        byte keySector = 5;         // load "keys" from sector 5
        byte sector = 0;            // when reading / writing use sector 0
        byte block = 21;            // blocks range from 0-63
        Mifare_Command.KeyType keytype = Mifare_Command.KeyType.A;

        LoadKeys(scard, keySector, keytype);

        ReadData(scard, sector, block, keytype);

        var testDataString = create_test_data_string();
        var testDataBytes = Encoding.ASCII.GetBytes(testDataString);
        Console.WriteLine();
        Console.WriteLine("writing '{0}' to chip.", testDataString);

        WriteData(scard, sector, block, keytype, testDataBytes);

        ReadData(scard, sector, block, keytype);

        // for duali singlewire, SmartCardControl needs to be invoked after reading / writing data
        SmartCardControl(bidiSpl);

        Console.WriteLine();

        scardResult = scard.SCardDisConnect((int) scard_disposition.SCARD_LEAVE_CARD);
        Console.WriteLine("SCardDisConnect() result: {0}", scardResult);

        if (scard_error.SCARD_S_SUCCESS != (scard_error) scardResult) {
            var msg = SmartcardCommon.SCardResultMessage(scardResult, "");
            throw new Exception("SCardDisConnect() fail: " + msg);
        }
    }
    

    ////////////////////////////////////////////////////////////////////////////
    // Main()
    ////////////////////////////////////////////////////////////////////////////
    private static void Main(string[] args) {
        var commandLineOptions = GetCommandlineOptions(args);

        BidiSplWrap bidiSpl = null;
        int printerJobID = 0;

        try {
            bidiSpl = new BidiSplWrap();
            bidiSpl.BindDevice(commandLineOptions.printerName);

            // optional:
            string driverVersionXml = bidiSpl.GetPrinterData(strings.SDK_VERSION);
            Console.WriteLine(Environment.NewLine + "driver version: " + Util.ParseDriverVersionXML(driverVersionXml) + Environment.NewLine);

            string printerOptionsXml = bidiSpl.GetPrinterData(strings.PRINTER_OPTIONS2);
            PrinterOptionsValues printerOptionsValues = Util.ParsePrinterOptionsXML(printerOptionsXml);
            if ("Ready" != printerOptionsValues._printerStatus && "Busy" != printerOptionsValues._printerStatus) {
                throw new Exception(commandLineOptions.printerName + " is not ready. status: " + printerOptionsValues._printerStatus);
            }

            if ("Single wire" != printerOptionsValues._optionSmartcard) {
                var msg = string.Format("{0} needs 'Single wire' for smartcard option. '{1}' was returned.",
                    commandLineOptions.printerName,
                    printerOptionsValues._optionSmartcard);
                throw new Exception(msg);
            }

            string hopperID = string.Empty;
            printerJobID = Util.StartJob(
                bidiSpl,
                hopperID);

            SmartcardCommon.ParkCard(bidiSpl, commandLineOptions.parkBack);

            PersonalizeSmartcard(bidiSpl);

            SmartcardCommon.ResumeCard(bidiSpl, printerJobID, 0);

            if (commandLineOptions.print) {
                Util.PrintTextAndGraphics(bidiSpl, commandLineOptions.printerName);
                Util.WaitForWindowsJobID(bidiSpl, commandLineOptions.printerName);
            }

            bidiSpl.SetPrinterData(strings.ENDJOB);

            if (commandLineOptions.jobCompletion) {
                Util.PollForJobCompletion(bidiSpl, printerJobID);
            }
        }
        catch (BidiException e) {
            Console.WriteLine(e.Message);
            Util.CancelJob(bidiSpl, e.PrinterJobID, e.ErrorCode);
        }
        catch (Exception e) {
            Console.WriteLine(e.Message);
            if (0 != printerJobID) {
                Util.CancelJob(bidiSpl, printerJobID, 0);
            }
        }
        finally {
            bidiSpl.UnbindDevice();
        }
    }
}