/*
 * Copyright (c) 2015 Adobe Systems.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

/*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */
/*global require, exports, Buffer */
"use strict";

var CONSTANTS  = require("./USBMUXConstants"),
    bufferpack = require("bufferpack"),
    Logger     = require("./Logging");

var logger = Logger.createLogger({name: "USBMUXBinaryProtocol", level: "debug"});

/**
 * Initialize new instance.
 *
 * @param {!USBMUXSocket} usbmuxSocket  use this instance to connect to usbmuxd
 *
 * @constructor
 */
function USBMUXBinaryProtocol(usbmuxSocket) {
    if (!usbmuxSocket) {
        throw new Error("Please provide usbmuxSocket");
    }

    this.usbmuxSocket = usbmuxSocket;
    this.connected = false;

    logger.debug("created");
}

// Packet Types
USBMUXBinaryProtocol.prototype.TYPE_RESULT = 1;
USBMUXBinaryProtocol.prototype.TYPE_LISTEN = 3;
USBMUXBinaryProtocol.prototype.TYPE_DEVICE_ADD = 4;
USBMUXBinaryProtocol.prototype.TYPE_DEVICE_REMOVE = 5;
USBMUXBinaryProtocol.prototype.TYPE_DEVICE_CONNECT = 2;

/**
 * Return the protocolVersion
 * @readonly
 */
Object.defineProperty(USBMUXBinaryProtocol.prototype, "protocolVersion", {
    get: function() { return CONSTANTS.USBMUX_PROTOCOL_VERSION_BINARY; }
});

/**
 * Returns a string representation of this instance.
 * @returns {string}
 */
USBMUXBinaryProtocol.prototype.toString = function () {
    return "[USBMUXBinaryProtocol]";
};

/**
 * Decode a usbmux packet
 *
 * @param {!Buffer} data    the packet data
 * @returns {{response: Object, packetLabel: number, packetData: Object}}
 */
USBMUXBinaryProtocol.prototype.decodePacket = function (data) {
    if (this.connected) {
        throw new Error("Can't send control packets, since we already have a connection");
    }

    var unpacked = bufferpack.unpack("<I(messagelength)I(protocolversion)I(response)I(packetLabel)", data);

    // check if the protocol version does match
    if (unpacked.protocolversion !== this.protocolVersion) {
        throw new Error("Protocol version mismatch. Got " + unpacked.protocolversion + " but expected " + this.protocolVersion);
    }

    var payload = this.unwrapPacket(unpacked.response, data.slice(16));
    return {response: unpacked.response, packetLabel: unpacked.packetLabel, packetData: payload};
};
/**
 * Unwrap the content of the packet. This will return a object.
 *
 * @param {!string} response    the type of response
 * @param {!Buffer} payload the packet payload
 *
 * @returns {object}
 */
USBMUXBinaryProtocol.prototype.unwrapPacket = function (response, payload) {
    logger.debug("unwrapPacket");

    switch(response) {
        case this.TYPE_RESULT:
            return {"Number": bufferpack.unpack("I", payload)[0]};

        default:
            throw new Error("Unknown response type '" + response + "'");
    }
};

/**
 * Wrap the payload in a proper packet to send to usbmuxd. Create a buffer with protocol arguuments.
 *
 * @param {!string} request    the type of request
 * @param {!string} payload the packet payload. This is usually XML for this protocol
 *
 * @returns {Array|String}
 */
USBMUXBinaryProtocol.prototype.wrapPacket = function (request, payload) {
    logger.debug("wrapPacket", request);

    switch(request) {
        case this.TYPE_LISTEN:
            return "";

        case this.TYPE_DEVICE_CONNECT:
            return bufferpack.pack("IH", [payload.DeviceID, payload.PortNumber, 0x0, 0x0]);

        default:
            throw new Error("Unknown request type '" + request + "'");
    }
};

USBMUXBinaryProtocol.prototype.sendPacket = function (request, packetTag, payload) {
    logger.debug("sendPacket");

    if (this.connected) {
        throw new Error("Can't send control packets, since we already have a connection");
    }

    payload = payload || {};

    var _payload = this.wrapPacket(request, payload);

    _payload += "\n";
    var protoVersion = this.protocolVersion;
    // 16 === header overhead
    var payloadLength = 16 + _payload.length;
    var values = [payloadLength, protoVersion, request, packetTag];

    logger.log("payload", "protocol header: " + values);
    // little endian packaging
    var format = "<IIII";
    var packetData = bufferpack.pack(format, values);
    var buffer2 = new Buffer(_payload);
    var buffer3 = Buffer.concat([packetData, buffer2]);

    // use payload logger for raw packet data
    logger.log("payload", buffer3.toString());

    // send the packet
    this.usbmuxSocket.write(buffer3);
};

exports.USBMUXBinaryProtocol = USBMUXBinaryProtocol;
