// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved
//
// File Name:
//
//    USBMON_Bidi_JavaScript_File.js
//
// Abstract:
//
//    Sample USBMON Javascript extension file for v4 printer drivers.
//



/// Base64 Encode from 
///
/// @param  input - binary buffer
///
/// @return String - Encoding Result
///
function Encode64( input )
{
    var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
    var output = "";
    var chr1, chr2, chr3;
    var enc1, enc2, enc3, enc4;
    var i = 0;
    do {
        chr1 = input[i++];
        chr2 = input[i++];
        chr3 = input[i++];
        enc1 = chr1 >> 2;
        enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
        enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
        enc4 = chr3 & 63;
        if (isNaN(chr2)) {
            enc3 = enc4 = 64;
        } else if (isNaN(chr3)) {
            enc4 = 64;
        }
        output = output + keyStr.charAt(enc1) + keyStr.charAt(enc2) + keyStr.charAt(enc3) + keyStr.charAt(enc4);
    } while (i < input.length);
   
    return output;
}

/// Make PJL Command
///
/// @param  Query - PJL String
///
/// @return String - added prefix and postfix data
///
function GetPJLCommand(Query) {
    var Prefix = [0x1B, 0x25, 0x2D, 0x31, 0x32, 0x33, 0x34, 0x35, 0x58, 0x40, 0x50, 0x4A, 0x4C, 0x0D, 0x0A];
    var Postfix = [0x1B, 0x25, 0x2D, 0x31, 0x32, 0x33, 0x34, 0x35, 0x58];

    var Command = Prefix.concat(Query, Postfix);
    return Command;
}

/// Read Command from device
///
/// @param  printerStream - Printer stream for communication
/// @Param  Header - Command Header
/// @Param  bufferSize - Minimum Buffer Size
///
/// @return String - read data that exclude Header data.
///
function ReadString(printerStream, Header, bufferSize) {
    var nBufferSize = 50;
    if (bufferSize > nBufferSize)
        nBufferSize = bufferSize;

    var ReadData = "";
    for (var index = 0; index < 3; index++) {
        var readBuffer = printerStream.read(nBufferSize);
        if ((null === readBuffer) || (0 >= readBuffer.length))
            break;

        if (readBuffer.length <= Header.length)
            continue;

        var Data = "";
        for (var i = 0; i < readBuffer.length; i++) {
            Data += String.fromCharCode(readBuffer[i]);
        }

        var nPos = -1;
        if ("" !== Header)
            nPos = Data.search(Header);
            
        if (-1 < nPos) {
            ReadData = Data.substring(Header.length);
            break;
        }
    }
    return ReadData;
}

/// <summary>
///    Get the requested Schema(s).
///
///    The script can use the 'schemaRequests' object to iterate through the Query Keys requested by the user. Based on the query keys,
///    the script should use the 'printerStream' object to communicate with the USB print device and determine the values of one or more Bidi
///    Schema elements. For each Bidi Schema element the new value can be returned to the caller by using functions of the 'printerBidiSchemaResponses'
///    object. Once all query keys have been processed and all values added to the 'printerBidiSchemaResponses' object the script can return.
///
///    It is possible the attached device is not ready to return some of the requested data. In this case the function can return a value of 1 to indicate the call
///    should be retried after a wait.
/// </summary>
/// <param name="scriptContext" type="IPrinterScriptContext">
///     Script context object.
/// </param>
/// <param name="printerStream" type="IPrinterScriptableSequentialStream">
///    Allows the script to Write/Read data from the attached USB device.
/// </param>
/// <param name="schemaRequests" type="Array">
///    Array of strings that contains all the requested Query Keys.
/// </param>
/// <param name="printerBidiSchemaResponses" type="IPrinterBidiSchemaResponses">
///    Object the script will use to store all responses to query keys.
/// </param>
/// <returns type="Number" integer="true">
///     Integer value indicating function completion status.
///         1 - The attached device was not ready to provide some requested information. Call the function again using any Requery Keys added during processing.
///         0 - The script completed successfuly.
/// </returns>
function getSchemas( scriptContext, printerStream, schemaRequests,  printerBidiSchemaResponses ) 
{
    var retVal = 0;
    var i = 0;

    debugger;

    var bag = scriptContext.QueueProperties;
    var queueProperty = bag.GetString("Config:Win8Support");
    
    //Loop through all the QueryKeys provided in the schemaRequests object
    for ( var index = 0; index < schemaRequests.length; index++ ) {
        var key = schemaRequests[index];  
        
        if (("On" !== queueProperty) && ("DEVICEID" !== key)) {
            if ("Off" !== queueProperty)  {
                retVal = 1;
                printerBidiSchemaResponses.addRequeryKey(key);
            }
            continue;
        }
        
        if (key === "DEVICEID") {
            //Request:  @PJL INFO ID
            //Response: @PJL INFO ID\n"Dell B1260dn Mono Laser Printer"
            var bId = false;
            var Id = "";
            var Data = [0x40, 0x50, 0x4A, 0x4C, 0x20, 0x49, 0x4E, 0x46, 0x4F, 0x20, 0x49, 0x44, 0x0D, 0x0A];
            var writeData = GetPJLCommand(Data);
            var bytesWritten = printerStream.write(writeData);
            if (bytesWritten === writeData.length) {
                var result = ReadString(printerStream, "@PJL INFO ID", 128);
                if ("" !== result) {
                    var nStart = result.indexOf('\"');
                    var nEnd = result.lastIndexOf('\"');
                    Id = result.substring(nStart+1, nEnd);
                    if ("" !== Id)
                        bId = true;
                }
            }
            if (false === bId) {
                continue;
            }

            printerBidiSchemaResponses.addString("\\Printer.DeviceInfo:Id", Id);

            //Request:  @PJL GETDEVICEID
            //Response: @PJL GETDEVICEID "deviceid.."
            var bDeviceId = false;
            var DeviceId = "";
            var Data = [0x40, 0x50, 0x4A, 0x4C, 0x20, 0x47, 0x45, 0x54, 0x44, 0x45, 0x56, 0x49, 0x43, 0x45, 0x49, 0x44, 0x0D, 0x0A];
            var writeData = GetPJLCommand(Data);
            var bytesWritten = printerStream.write( writeData );
            if (bytesWritten === writeData.length) {
                var result = ReadString(printerStream, "@PJL GETDEVICEID=", 512);
                DeviceId = result.replace(/\"/g, '');
                if ("" !== DeviceId)
                {
                    bDeviceId = true;
                    if (-1 < DeviceId.search(/win8/i))
                        printerBidiSchemaResponses.addBool("\\Printer.Dell.AutoConfiguration:Supported", true);
                }
            }
            if (false === bDeviceId) {
                if (bId)
                    printerBidiSchemaResponses.addBool("\\Printer.Dell.AutoConfiguration:Supported", false);
            }

            printerBidiSchemaResponses.addString("\\Printer.DeviceInfo:DeviceId", DeviceId);
            
        }
        else if (key === "DEVICEIMAGE") {
            //Request:  1B 91 00 00 00 00 00 03 00 00 00 01 00 00 00 00
            //Response: 1B 91 00 00 00 00 00 03 00 00 00 01 01 12 24 33
            //          Image data
            var ImageData = "";
            var writeData = [0x1B, 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00];
            var bytesWritten = printerStream.write(writeData);
            if (bytesWritten === writeData.length) {
                for (i = 0; i < 3; i++) {
                    var readBuffer = printerStream.read(50);
                    if ((null === readBuffer) || (0 === readBuffer.length))
                        break;

                    if (readBuffer.length == 16) {
                        if ((readBuffer[0] !== 0x1B) || (readBuffer[1] !== 0x91))
                            continue;

                        var size = 0;
                        for (i = 0; i < 4; i++) {
                            size *= 256;
                            size += readBuffer[12 + i];
                        }
                        if (size > 0) {
                            var readBuffer2 = printerStream.read(size);
                            if ((null != readBuffer2) && (readBuffer2.length === size)) {
                                ImageData = Encode64(readBuffer2);
                            }
                        }
                        break;
                    }
                }
            }
            printerBidiSchemaResponses.addString("\\Printer.DeviceInfo:Image", ImageData);
        }
        else if (key === "AUTOCONFIG_DATA") {
            //Request:  @PJL AUTOCONFIG
            //Response: @PJL AUTOCONFIG <xml> .....</xml>
            var AutoConfigXml = "";
            var Data = [0x40, 0x50, 0x4A, 0x4C, 0x20, 0x41, 0x55, 0x54, 0x4F, 0x43, 0x4F, 0x4E, 0x46, 0x49, 0x47, 0x0D, 0x0A];
            var writeData = GetPJLCommand(Data);
            var bytesWritten = printerStream.write( writeData );
            if (bytesWritten === writeData.length) {
                AutoConfigXml = ReadString(printerStream, "@PJL AUTOCONFIG ", 10240);
            }
            printerBidiSchemaResponses.addString("\\Printer.Dell.AutoConfiguration:Data", AutoConfigXml);
        }
        
    }
    return retVal;
}

///
/// Set a requested Bidi Schema Value in the device
///
/// @param  scriptContext - Script context object which allows access to IPrinterScriptablePropertyBag for printer driver and queue properties
/// @param  printerStream - Allows the script to Write/Read data from the attached USB device
/// @param  printerBidiSchemaElement - Contains all the data associated with the Bidi Schema Value to set
/// 
/// @return Integer value - currently only JavaScript exceptions are expected for failures
///
/// The script can interpret the incoming Bidi Schema value to either set data in the device or perform an action on the device. 
///
/// The scriptContext object provides access to the Driver and Queue prorperty bags associated with the current device. These property bags support read-only access.
/// 
function setSchema( scriptContext, printerStream, printerBidiSchemaElement ) 
{ 
    return 0;
}

/// <summary>
///    Retrieve unsolicited Bidi Schema value updates from the USB device during printing.
///
///    This function is only called when a job is printing. A device can provide data on the read channel which this script can interpret into
///    Bidi Schema values and returned to USBMon.
///
///    This function will be called repeatedly during printing. It is expected the device will only return data if it is available and the script can understand it.
///    If the device does not support querying for unsolicited status or the script can determine that there is no need to call this function again, the script can return
///    a value of 2 which will tell the getStatus execution thread in USBMon to exit successfully.
///
///    If the print device does not support retrieving status during a print job this function should be left out of the driver's JavaScipt file altogether. This will inform 
///    USBMon to skip invocation of the function.
/// </summary>
/// <param name="scriptContext" type="IPrinterScriptContext">
///    Accessor for PropertyBags for printer driver and queue properties.
/// </param>
/// <param name="printerStream" type="IPrinterScriptableSequentialStream">
///    Allows the script to read data from the attached USB device. Calling the write function will fail. This device is opened read-only for this function.
/// </param>
/// <param name="printerBidiSchemaResponses" type="IPrinterBidiSchemaResponses">
///    Object the script will use to store all status responses.
/// </param>
/// <returns type="Number" integer="true">
///     Integer value indicating function completion status.
///         2 - The device no longer (if ever) supports unsolicited status so no need for USBMon to make more calls to this function.
///         0 - The script completed successfuly.
/// </returns>
function getStatus( scriptContext, printerStream, printerBidiSchemaResponses ) 
{ 
    var retVal = 0;
   
    debugger;
    
    var bag = scriptContext.QueueProperties;
    var queueProperty = bag.GetString("Config:Win8Support");
    if ("Off" === queueProperty) {
        return 2;
    }

    //sample: @PJL DRIVERSTATUS STATUSCODE=00040002
    var bReadStatus = false;
    var readBuffer = printerStream.read(50);
    if (null !== readBuffer) {
        var bytesRead = readBuffer.length;
        var Header = "@PJL DRIVERSTATUS STATUSCODE=";

        if (bytesRead > Header.length) {
            var Read = "";
            for (i = 0; i < bytesRead; i++)
                Read += String.fromCharCode(readBuffer[i]);

            if (-1 < Read.search(Header)) {
                var Category = Read.substr(Header.length, 4).toUpperCase();
                var Status = Read.substr(Header.length + 4, 4).toUpperCase();

                if (Category === "0001") {      //None
                    if (Status === "0001")
                        printerBidiSchemaResponses.addString("\\Printer.Status.Summary:StateReason", "None");
                }
                else if (Category === "0002") { //Door
                    if (Status === "0001")
                        printerBidiSchemaResponses.addString("\\Printer.Status.Summary:StateReason", "DoorOpen");
                }
                else if (Category === "0003") { //Marker
                    if (Status === "0001")
                        printerBidiSchemaResponses.addString("\\Printer.Status.Summary:StateReason", "MarkerSupplyLow");
                    else if (Status === "0002")
                        printerBidiSchemaResponses.addString("\\Printer.Status.Summary:StateReason", "MarkerFailure");
                    else if (Status === "0003")
                        printerBidiSchemaResponses.addString("\\Printer.Status.Summary:StateReason", "MarkerSupplyEmpty");
                }
                else if (Category === "0004") { //Media
                    if (Status === "0001")
                        printerBidiSchemaResponses.addString("\\Printer.Status.Summary:StateReason", "MediaLow");
                    else if (Status === "0002")
                        printerBidiSchemaResponses.addString("\\Printer.Status.Summary:StateReason", "MediaEmpty");
                    else if (Status === "0003")
                        printerBidiSchemaResponses.addString("\\Printer.Status.Summary:StateReason", "MediaJam");
                    else if (Status === "0004")
                        printerBidiSchemaResponses.addString("\\Printer.Status.Summary:StateReason", "MediaNeeded");
                }
                else if (Category === "0005") { //Output
                    if (Status === "0001")
                        printerBidiSchemaResponses.addString("\\Printer.Status.Summary:StateReason", "OutputAreaAlmostFull");
                    else if (Status === "0002")
                        printerBidiSchemaResponses.addString("\\Printer.Status.Summary:StateReason", "OutputAreaFull");
                }
                else if (Category === "F000") { //AttentionRequired
                    if (Status === "0001")
                        printerBidiSchemaResponses.addString("\\Printer.Status.Summary:StateReason", "AttentionRequired");
                }
                else if (Category === "F100") { //Paused
                    if (Status === "0001")
                        printerBidiSchemaResponses.addString("\\Printer.Status.Summary:StateReason", "Paused");
                }
                else if (Category === "FFFF") { //Job End
                    if (Status === "FFFF") {
                        printerBidiSchemaResponses.addString("\\Printer.Status.Summary:StateReason", "None");
                        retVal = 2;
                    }
                }

                bReadStatus = true;
            }
        }
    }
    
    if ("None" === queueProperty) {
        if (bReadStatus) {
            printerBidiSchemaResponses.addBool("\\Printer.Dell.AutoConfiguration:Supported", true);
        }
        else {
            printerBidiSchemaResponses.addBool("\\Printer.Dell.AutoConfiguration:Supported", false);
            retVal = 2;
        }
    }
    
   return retVal;
}
