"use strict";
/*
   Copyright (C) 2012 by Jeremy P. White <jwhite@codeweavers.com>

   This file is part of spice-html5.

   spice-html5 is free software: you can redistribute it and/or modify
   it under the terms of the GNU Lesser General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   spice-html5 is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public License
   along with spice-html5.  If not, see <http://www.gnu.org/licenses/>.
*/

/*----------------------------------------------------------------------------
**  Spice messages
**      This file contains classes for passing messages to and from
**  a spice server.  This file should arguably be generated from
**  spice.proto, but it was instead put together by hand.
**--------------------------------------------------------------------------*/

import { Constants } from './enums.js';
import { SpiceDataView } from './spicedataview.js';
import { create_rsa_from_mb } from './ticket.js';
import {
  SpiceChannelId,
  SpiceRect,
  SpiceClip,
  SpiceCopy,
  SpiceFill,
  SpicePoint,
  SpiceSurface,
  SpicePoint16,
  SpiceCursor,
} from './spicetype.js';
import {
  keycode_to_start_scan,
  keycode_to_end_scan,
} from './utils.js';

function SpiceLinkHeader(a, at)
{
    this.magic = Constants.SPICE_MAGIC;
    this.major_version = Constants.SPICE_VERSION_MAJOR;
    this.minor_version = Constants.SPICE_VERSION_MINOR;
    this.size = 0;
    if (a !== undefined)
        this.from_buffer(a, at);
}

SpiceLinkHeader.prototype =
{
    from_buffer: function(a, at)
    {
        at = at || 0;
        var dv = new SpiceDataView(a);
        this.magic = "";
        for (var i = 0; i < 4; i++)
            this.magic += String.fromCharCode(dv.getUint8(at + i));
        at += 4;

        this.major_version = dv.getUint32(at, true); at += 4;
        this.minor_version = dv.getUint32(at, true); at += 4;
        this.size = dv.getUint32(at, true); at += 4;
    },

    to_buffer: function(a, at)
    {
        at = at || 0;
        var dv = new SpiceDataView(a);
        for (var i = 0; i < 4; i++)
            dv.setUint8(at + i, this.magic.charCodeAt(i));
        at += 4;

        dv.setUint32(at, this.major_version, true); at += 4;
        dv.setUint32(at, this.minor_version, true); at += 4;
        dv.setUint32(at, this.size, true); at += 4;
    },
    buffer_size: function()
    {
        return 16;
    },
}

function SpiceLinkMess(a, at)
{
    this.connection_id = 0;
    this.channel_type = 0;
    this.channel_id = 0;
    this.common_caps = [];
    this.channel_caps = [];

    if (a !== undefined)
        this.from_buffer(a, at);
}

SpiceLinkMess.prototype =
{
    from_buffer: function(a, at)
    {
        at = at || 0;
        var i;
        var orig_at = at;
        var dv = new SpiceDataView(a);
        this.connection_id = dv.getUint32(at, true); at += 4;
        this.channel_type = dv.getUint8(at, true); at++;
        this.channel_id = dv.getUint8(at, true); at++;
        var num_common_caps = dv.getUint32(at, true); at += 4;
        var num_channel_caps  = dv.getUint32(at, true); at += 4;
        var caps_offset = dv.getUint32(at, true); at += 4;

        at = orig_at + caps_offset;
        this.common_caps = [];
        for (i = 0; i < num_common_caps; i++)
        {
            this.common_caps.unshift(dv.getUint32(at, true)); at += 4;
        }

        this.channel_caps = [];
        for (i = 0; i < num_channel_caps; i++)
        {
            this.channel_caps.unshift(dv.getUint32(at, true)); at += 4;
        }
    },

    to_buffer: function(a, at)
    {
        at = at || 0;
        var orig_at = at;
        var i;
        var dv = new SpiceDataView(a);
        dv.setUint32(at, this.connection_id, true); at += 4;
        dv.setUint8(at, this.channel_type, true); at++;
        dv.setUint8(at, this.channel_id, true); at++;
        dv.setUint32(at, this.common_caps.length, true); at += 4;
        dv.setUint32(at, this.channel_caps.length, true); at += 4;
        dv.setUint32(at, (at - orig_at) + 4, true); at += 4;

        for (i = 0; i < this.common_caps.length; i++)
        {
            dv.setUint32(at, this.common_caps[i], true); at += 4;
        }

        for (i = 0; i < this.channel_caps.length; i++)
        {
            dv.setUint32(at, this.channel_caps[i], true); at += 4;
        }
    },
    buffer_size: function()
    {
        return 18 + (4 * this.common_caps.length) + (4 * this.channel_caps.length);
    }
}

function SpiceLinkReply(a, at)
{
    this.error = 0;
    this.pub_key = undefined;
    this.common_caps = [];
    this.channel_caps = [];

    if (a !== undefined)
        this.from_buffer(a, at);
}

SpiceLinkReply.prototype =
{
    from_buffer: function(a, at)
    {
        at = at || 0;
        var i;
        var orig_at = at;
        var dv = new SpiceDataView(a);
        this.error = dv.getUint32(at, true); at += 4;

        this.pub_key = create_rsa_from_mb(a, at);
        at += Constants.SPICE_TICKET_PUBKEY_BYTES;

        var num_common_caps = dv.getUint32(at, true); at += 4;
        var num_channel_caps  = dv.getUint32(at, true); at += 4;
        var caps_offset = dv.getUint32(at, true); at += 4;

        at = orig_at + caps_offset;
        this.common_caps = [];
        for (i = 0; i < num_common_caps; i++)
        {
            this.common_caps.unshift(dv.getUint32(at, true)); at += 4;
        }

        this.channel_caps = [];
        for (i = 0; i < num_channel_caps; i++)
        {
            this.channel_caps.unshift(dv.getUint32(at, true)); at += 4;
        }
    },
}

function SpiceLinkAuthTicket(a, at)
{
    this.auth_mechanism = 0;
    this.encrypted_data = undefined;
}

SpiceLinkAuthTicket.prototype =
{
    to_buffer: function(a, at)
    {
        at = at || 0;
        var i;
        var dv = new SpiceDataView(a);
        dv.setUint32(at, this.auth_mechanism, true); at += 4;
        for (i = 0; i < Constants.SPICE_TICKET_KEY_PAIR_LENGTH / 8; i++)
        {
            if (this.encrypted_data && i < this.encrypted_data.length)
                dv.setUint8(at, this.encrypted_data[i], true);
            else
                dv.setUint8(at, 0, true);
            at++;
        }
    },
    buffer_size: function()
    {
        return 4 + (Constants.SPICE_TICKET_KEY_PAIR_LENGTH / 8);
    }
}

function SpiceLinkAuthReply(a, at)
{
    this.auth_code = 0;
    if (a !== undefined)
        this.from_buffer(a, at);
}

SpiceLinkAuthReply.prototype =
{
    from_buffer: function(a, at)
    {
        at = at || 0;
        var dv = new SpiceDataView(a);
        this.auth_code = dv.getUint32(at, true); at += 4;
    },
    buffer_size: function()
    {
        return 4;
    }
}

function SpiceMiniData(a, at)
{
    this.type = 0;
    this.size = 0;
    this.data = undefined;
    if (a !== undefined)
        this.from_buffer(a, at);
}

SpiceMiniData.prototype =
{
    from_buffer: function(a, at)
    {
        at = at || 0;
        var i;
        var dv = new SpiceDataView(a);
        this.type = dv.getUint16(at, true); at += 2;
        this.size = dv.getUint32(at, true); at += 4;
        if (a.byteLength > at)
        {
            this.data = a.slice(at);
            at += this.data.byteLength;
        }
    },
    to_buffer : function(a, at)
    {
        at = at || 0;
        var i;
        var dv = new SpiceDataView(a);
        dv.setUint16(at, this.type, true); at += 2;
        dv.setUint32(at, this.data ? this.data.byteLength : 0, true); at += 4;
        if (this.data && this.data.byteLength > 0)
        {
            var u8arr = new Uint8Array(this.data);
            for (i = 0; i < u8arr.length; i++, at++)
                dv.setUint8(at, u8arr[i], true);
        }
    },
    build_msg : function(in_type,  extra)
    {
        this.type = in_type;
        this.size = extra.buffer_size();
        this.data = new ArrayBuffer(this.size);
        extra.to_buffer(this.data);
    },
    buffer_size: function()
    {
        if (this.data)
            return 6 + this.data.byteLength;
        else
            return 6;
    },
}

function SpiceMsgChannels(a, at)
{
    this.num_of_channels = 0;
    this.channels = [];
    if (a !== undefined)
        this.from_buffer(a, at);
}

SpiceMsgChannels.prototype =
{
    from_buffer: function(a, at)
    {
        at = at || 0;
        var i;
        var dv = new SpiceDataView(a);
        this.num_of_channels = dv.getUint32(at, true); at += 4;
        for (i = 0; i < this.num_of_channels; i++)
        {
            var chan = new SpiceChannelId();
            at = chan.from_dv(dv, at, a);
            this.channels.push(chan);
        }
    },
}

function SpiceMsgMainInit(a, at)
{
    this.from_buffer(a, at);
}

SpiceMsgMainInit.prototype =
{
    from_buffer: function(a, at)
    {
        at = at || 0;
        var dv = new SpiceDataView(a);
        this.session_id = dv.getUint32(at, true); at += 4;
        this.display_channels_hint = dv.getUint32(at, true); at += 4;
        this.supported_mouse_modes = dv.getUint32(at, true); at += 4;
        this.current_mouse_mode = dv.getUint32(at, true); at += 4;
        this.agent_connected = dv.getUint32(at, true); at += 4;
        this.agent_tokens = dv.getUint32(at, true); at += 4;
        this.multi_media_time = dv.getUint32(at, true); at += 4;
        this.ram_hint = dv.getUint32(at, true); at += 4;
    },
}

function SpiceMsgMainMouseMode(a, at)
{
    this.from_buffer(a, at);
}

SpiceMsgMainMouseMode.prototype =
{
    from_buffer: function(a, at)
    {
        at = at || 0;
        var dv = new SpiceDataView(a);
        this.supported_modes = dv.getUint16(at, true); at += 2;
        this.current_mode = dv.getUint16(at, true); at += 2;
    },
}

function SpiceMsgMainAgentData(a, at)
{
    this.from_buffer(a, at);
}

SpiceMsgMainAgentData.prototype =
{
    from_buffer: function(a, at)
    {
        at = at || 0;
        var dv = new SpiceDataView(a);
        this.protocol = dv.getUint32(at, true); at += 4;
        this.type = dv.getUint32(at, true); at += 4;
        this.opaque = dv.getUint64(at, true); at += 8;
        this.size = dv.getUint32(at, true); at += 4;
        if (a.byteLength > at)
        {
            this.data = a.slice(at);
            at += this.data.byteLength;
        }
    }
}

function SpiceMsgMainAgentTokens(a, at)
{
    this.from_buffer(a, at);
}

SpiceMsgMainAgentTokens.prototype =
{
    from_buffer: function(a, at)
    {
        at = at || 0;
        var dv = new SpiceDataView(a);
        this.num_tokens = dv.getUint32(at, true); at += 4;
    },
}

function SpiceMsgSetAck(a, at)
{
    this.from_buffer(a, at);
}

SpiceMsgSetAck.prototype =
{
    from_buffer: function(a, at)
    {
        at = at || 0;
        var dv = new SpiceDataView(a);
        this.generation = dv.getUint32(at, true); at += 4;
        this.window = dv.getUint32(at, true); at += 4;
    },
}

function SpiceMsgcAckSync(ack)
{
    this.generation = ack.generation;
}

SpiceMsgcAckSync.prototype =
{
    to_buffer: function(a, at)
    {
        at = at || 0;
        var dv = new SpiceDataView(a);
        dv.setUint32(at, this.generation, true); at += 4;
    },
    buffer_size: function()
    {
        return 4;
    }
}

function SpiceMsgcMainMouseModeRequest(mode)
{
    this.mode = mode;
}

SpiceMsgcMainMouseModeRequest.prototype =
{
    to_buffer: function(a, at)
    {
        at = at || 0;
        var dv = new SpiceDataView(a);
        dv.setUint16(at, this.mode, true); at += 2;
    },
    buffer_size: function()
    {
        return 2;
    }
}

function SpiceMsgcMainAgentStart(num_tokens)
{
    this.num_tokens = num_tokens;
}

SpiceMsgcMainAgentStart.prototype =
{
    to_buffer: function(a, at)
    {
        at = at || 0;
        var dv = new SpiceDataView(a);
        dv.setUint32(at, this.num_tokens, true); at += 4;
    },
    buffer_size: function()
    {
        return 4;
    }
}

function SpiceMsgcMainAgentData(type, data)
{
    this.protocol = Constants.VD_AGENT_PROTOCOL;
    this.type = type;
    this.opaque = 0;
    this.size = data.buffer_size();
    this.data = data;
}

SpiceMsgcMainAgentData.prototype =
{
    to_buffer: function(a, at)
    {
        at = at || 0;
        var dv = new SpiceDataView(a);
        dv.setUint32(at, this.protocol, true); at += 4;
        dv.setUint32(at, this.type, true); at += 4;
        dv.setUint64(at, this.opaque, true); at += 8;
        dv.setUint32(at, this.size, true); at += 4;
        this.data.to_buffer(a, at);
    },
    buffer_size: function()
    {
        return 4 + 4 + 8 + 4 + this.data.buffer_size();
    }
}

function VDAgentAnnounceCapabilities(request, caps)
{
    if (caps)
    {
        this.request = request;
        this.caps = caps;
    }
    else
        this.from_buffer(request);
}

VDAgentAnnounceCapabilities.prototype =
{
    to_buffer: function(a, at)
    {
        at = at || 0;
        var dv = new SpiceDataView(a);
        dv.setUint32(at, this.request, true); at += 4;
        dv.setUint32(at, this.caps, true); at += 4;
    },
    from_buffer: function(a, at)
    {
        at = at || 0;
        var dv = new SpiceDataView(a);
        this.request = dv.getUint32(at, true); at += 4;
        this.caps = dv.getUint32(at, true); at += 4;
        return at;
    },
    buffer_size: function()
    {
        return 8;
    }
}

function VDAgentMonitorsConfig(flags, width, height, depth, x, y)
{
    this.num_mon = 1;
    this.flags = flags;
    this.width = width;
    this.height = height;
    this.depth = depth;
    this.x = x;
    this.y = y;
}

VDAgentMonitorsConfig.prototype =
{
    to_buffer: function(a, at)
    {
        at = at || 0;
        var dv = new SpiceDataView(a);
        dv.setUint32(at, this.num_mon, true); at += 4;
        dv.setUint32(at, this.flags, true); at += 4;
        dv.setUint32(at, this.height, true); at += 4;
        dv.setUint32(at, this.width, true); at += 4;
        dv.setUint32(at, this.depth, true); at += 4;
        dv.setUint32(at, this.x, true); at += 4;
        dv.setUint32(at, this.y, true); at += 4;
    },
    buffer_size: function()
    {
        return 28;
    }
}

function VDAgentFileXferStatusMessage(data, result)
{
    if (result)
    {
        this.id = data;
        this.result = result;
    }
    else
        this.from_buffer(data);
}

VDAgentFileXferStatusMessage.prototype =
{
    to_buffer: function(a, at)
    {
        at = at || 0;
        var dv = new SpiceDataView(a);
        dv.setUint32(at, this.id, true); at += 4;
        dv.setUint32(at, this.result, true); at += 4;
    },
    from_buffer: function(a, at)
    {
        at = at || 0;
        var dv = new SpiceDataView(a);
        this.id = dv.getUint32(at, true); at += 4;
        this.result = dv.getUint32(at, true); at += 4;
        return at;
    },
    buffer_size: function()
    {
        return 8;
    }
}

function VDAgentFileXferStartMessage(id, name, size)
{
    this.id = id;
    this.string = "[vdagent-file-xfer]\n"+"name="+name+"\nsize="+size+"\n";
}

VDAgentFileXferStartMessage.prototype =
{
    to_buffer: function(a, at)
    {
        at = at || 0;
        var dv = new SpiceDataView(a);
        dv.setUint32(at, this.id, true); at += 4;
        for (var i = 0; i < this.string.length; i++, at++)
            dv.setUint8(at, this.string.charCodeAt(i));
    },
    buffer_size: function()
    {
        return 4 + this.string.length + 1;
    }
}

function VDAgentFileXferDataMessage(id, size, data)
{
    this.id = id;
    this.size = size;
    this.data = data;
}

VDAgentFileXferDataMessage.prototype =
{
    to_buffer: function(a, at)
    {
        at = at || 0;
        var dv = new SpiceDataView(a);
        dv.setUint32(at, this.id, true); at += 4;
        dv.setUint64(at, this.size, true); at += 8;
        if (this.data && this.data.byteLength > 0)
        {
            var u8arr = new Uint8Array(this.data);
            for (var i = 0; i < u8arr.length; i++, at++)
                dv.setUint8(at, u8arr[i]);
        }
    },
    buffer_size: function()
    {
        return 12 + this.size;
    }
}

function SpiceMsgNotify(a, at)
{
    this.from_buffer(a, at);
}

SpiceMsgNotify.prototype =
{
    from_buffer: function(a, at)
    {
        at = at || 0;
        var i;
        var dv = new SpiceDataView(a);
        this.time_stamp = dv.getUint64(at, true); at += 8;
        this.severity = dv.getUint32(at, true); at += 4;
        this.visibility = dv.getUint32(at, true); at += 4;
        this.what = dv.getUint32(at, true); at += 4;
        this.message_len = dv.getUint32(at, true); at += 4;
        this.message = "";
        for (i = 0; i < this.message_len; i++)
        {
            var c = dv.getUint8(at, true); at++;
            this.message += String.fromCharCode(c);
        }
    },
}

function SpiceMsgcDisplayInit()
{
    this.pixmap_cache_id = 1;
    this.glz_dictionary_id = 0;
    this.pixmap_cache_size = 10 * 1024 * 1024;
    this.glz_dictionary_window_size = 0;
}

SpiceMsgcDisplayInit.prototype =
{
    to_buffer: function(a, at)
    {
        at = at || 0;
        var dv = new SpiceDataView(a);
        dv.setUint8(at, this.pixmap_cache_id, true); at++;
        dv.setUint64(at, this.pixmap_cache_size, true); at += 8;
        dv.setUint8(at, this.glz_dictionary_id, true); at++;
        dv.setUint32(at, this.glz_dictionary_window_size, true); at += 4;
    },
    buffer_size: function()
    {
        return 14;
    }
}

function SpiceMsgDisplayBase()
{
}

SpiceMsgDisplayBase.prototype =
{
    from_dv : function(dv, at, mb)
    {
        this.surface_id = dv.getUint32(at, true); at += 4;
        this.box = new SpiceRect;
        at = this.box.from_dv(dv, at, mb);
        this.clip = new SpiceClip;
        return this.clip.from_dv(dv, at, mb);
    },
}

function SpiceMsgDisplayDrawCopy(a, at)
{
    this.from_buffer(a, at);
}

SpiceMsgDisplayDrawCopy.prototype =
{
    from_buffer: function(a, at)
    {
        at = at || 0;
        var dv = new SpiceDataView(a);
        this.base = new SpiceMsgDisplayBase;
        at = this.base.from_dv(dv, at, a);
        this.data = new SpiceCopy;
        return this.data.from_dv(dv, at, a);
    },
}

function SpiceMsgDisplayDrawFill(a, at)
{
    this.from_buffer(a, at);
}

SpiceMsgDisplayDrawFill.prototype =
{
    from_buffer: function(a, at)
    {
        at = at || 0;
        var dv = new SpiceDataView(a);
        this.base = new SpiceMsgDisplayBase;
        at = this.base.from_dv(dv, at, a);
        this.data = new SpiceFill;
        return this.data.from_dv(dv, at, a);
    },
}

function SpiceMsgDisplayCopyBits(a, at)
{
    this.from_buffer(a, at);
}

SpiceMsgDisplayCopyBits.prototype =
{
    from_buffer: function(a, at)
    {
        at = at || 0;
        var dv = new SpiceDataView(a);
        this.base = new SpiceMsgDisplayBase;
        at = this.base.from_dv(dv, at, a);
        this.src_pos = new SpicePoint;
        return this.src_pos.from_dv(dv, at, a);
    },
}


function SpiceMsgSurfaceCreate(a, at)
{
    this.from_buffer(a, at);
}

SpiceMsgSurfaceCreate.prototype =
{
    from_buffer: function(a, at)
    {
        at = at || 0;
        var dv = new SpiceDataView(a);
        this.surface = new SpiceSurface;
        return this.surface.from_dv(dv, at, a);
    },
}

function SpiceMsgSurfaceDestroy(a, at)
{
    this.from_buffer(a, at);
}

SpiceMsgSurfaceDestroy.prototype =
{
    from_buffer: function(a, at)
    {
        at = at || 0;
        var dv = new SpiceDataView(a);
        this.surface_id = dv.getUint32(at, true); at += 4;
    },
}

function SpiceMsgInputsInit(a, at)
{
    this.from_buffer(a, at);
}

SpiceMsgInputsInit.prototype =
{
    from_buffer: function(a, at)
    {
        at = at || 0;
        var dv = new SpiceDataView(a);
        this.keyboard_modifiers = dv.getUint16(at, true); at += 2;
        return at;
    },
}

function SpiceMsgInputsKeyModifiers(a, at)
{
    this.from_buffer(a, at);
}

SpiceMsgInputsKeyModifiers.prototype =
{
    from_buffer: function(a, at)
    {
        at = at || 0;
        var dv = new SpiceDataView(a);
        this.keyboard_modifiers = dv.getUint16(at, true); at += 2;
        return at;
    },
}

function SpiceMsgCursorInit(a, at)
{
    this.from_buffer(a, at);
}

SpiceMsgCursorInit.prototype =
{
    from_buffer: function(a, at, mb)
    {
        at = at || 0;
        var dv = new SpiceDataView(a);
        this.position = new SpicePoint16;
        at = this.position.from_dv(dv, at, mb);
        this.trail_length = dv.getUint16(at, true); at += 2;
        this.trail_frequency = dv.getUint16(at, true); at += 2;
        this.visible = dv.getUint8(at, true); at ++;
        this.cursor = new SpiceCursor;
        return this.cursor.from_dv(dv, at, a);
    },
}

function SpiceMsgPlaybackData(a, at)
{
    this.from_buffer(a, at);
}

SpiceMsgPlaybackData.prototype =
{
    from_buffer: function(a, at, mb)
    {
        at = at || 0;
        var dv = new SpiceDataView(a);
        this.time = dv.getUint32(at, true); at += 4;
        if (a.byteLength > at)
        {
            this.data = a.slice(at);
            at += this.data.byteLength;
        }
        return at;
    },
}

function SpiceMsgPlaybackMode(a, at)
{
    this.from_buffer(a, at);
}

SpiceMsgPlaybackMode.prototype =
{
    from_buffer: function(a, at, mb)
    {
        at = at || 0;
        var dv = new SpiceDataView(a);
        this.time = dv.getUint32(at, true); at += 4;
        this.mode = dv.getUint16(at, true); at += 2;
        if (a.byteLength > at)
        {
            this.data = a.slice(at);
            at += this.data.byteLength;
        }
        return at;
    },
}

function SpiceMsgPlaybackStart(a, at)
{
    this.from_buffer(a, at);
}

SpiceMsgPlaybackStart.prototype =
{
    from_buffer: function(a, at, mb)
    {
        at = at || 0;
        var dv = new SpiceDataView(a);
        this.channels = dv.getUint32(at, true); at += 4;
        this.format = dv.getUint16(at, true); at += 2;
        this.frequency = dv.getUint32(at, true); at += 4;
        this.time = dv.getUint32(at, true); at += 4;
        return at;
    },
}



function SpiceMsgCursorSet(a, at)
{
    this.from_buffer(a, at);
}

SpiceMsgCursorSet.prototype =
{
    from_buffer: function(a, at, mb)
    {
        at = at || 0;
        var dv = new SpiceDataView(a);
        this.position = new SpicePoint16;
        at = this.position.from_dv(dv, at, mb);
        this.visible = dv.getUint8(at, true); at ++;
        this.cursor = new SpiceCursor;
        return this.cursor.from_dv(dv, at, a);
    },
}


function SpiceMsgcMousePosition(sc, e)
{
    // FIXME - figure out how to correctly compute display_id
    this.display_id = 0;
    this.buttons_state = sc.buttons_state;
    if (e)
    {
        this.x = e.offsetX;
        this.y = e.offsetY;

        sc.mousex = e.offsetX;
        sc.mousey = e.offsetY;
    }
    else
    {
        this.x = this.y = this.buttons_state = 0;
    }
}

SpiceMsgcMousePosition.prototype =
{
    to_buffer: function(a, at)
    {
        at = at || 0;
        var dv = new SpiceDataView(a);
        dv.setUint32(at, this.x, true); at += 4;
        dv.setUint32(at, this.y, true); at += 4;
        dv.setUint16(at, this.buttons_state, true); at += 2;
        dv.setUint8(at, this.display_id, true); at += 1;
        return at;
    },
    buffer_size: function()
    {
        return 11;
    }
}

function SpiceMsgcMouseMotion(sc, e)
{
    // FIXME - figure out how to correctly compute display_id
    this.display_id = 0;
    this.buttons_state = sc.buttons_state;
    if (e)
    {
        this.x = e.offsetX;
        this.y = e.offsetY;

        if (sc.mousex !== undefined)
        {
            this.x -= sc.mousex;
            this.y -= sc.mousey;
        }
        sc.mousex = e.offsetX;
        sc.mousey = e.offsetY;
    }
    else
    {
        this.x = this.y = this.buttons_state = 0;
    }
}

/* Use the same functions as for MousePosition */
SpiceMsgcMouseMotion.prototype.to_buffer = SpiceMsgcMousePosition.prototype.to_buffer;
SpiceMsgcMouseMotion.prototype.buffer_size = SpiceMsgcMousePosition.prototype.buffer_size;

function SpiceMsgcMousePress(sc, e)
{
    if (e)
    {
        this.button = e.button + 1;
        this.buttons_state = 1 << e.button;
        sc.buttons_state = this.buttons_state;
    }
    else
    {
        this.button = Constants.SPICE_MOUSE_BUTTON_LEFT;
        this.buttons_state = Constants.SPICE_MOUSE_BUTTON_MASK_LEFT;
    }
}

SpiceMsgcMousePress.prototype =
{
    to_buffer: function(a, at)
    {
        at = at || 0;
        var dv = new SpiceDataView(a);
        dv.setUint8(at, this.button, true); at ++;
        dv.setUint16(at, this.buttons_state, true); at += 2;
        return at;
    },
    buffer_size: function()
    {
        return 3;
    }
}

function SpiceMsgcMouseRelease(sc, e)
{
    if (e)
    {
        this.button = e.button + 1;
        this.buttons_state = 0;
        sc.buttons_state = this.buttons_state;
    }
    else
    {
        this.button = Constants.SPICE_MOUSE_BUTTON_LEFT;
        this.buttons_state = 0;
    }
}

/* Use the same functions as for MousePress */
SpiceMsgcMouseRelease.prototype.to_buffer = SpiceMsgcMousePress.prototype.to_buffer;
SpiceMsgcMouseRelease.prototype.buffer_size = SpiceMsgcMousePress.prototype.buffer_size;


function SpiceMsgcKeyDown(e)
{
    if (e)
    {
        this.code = keycode_to_start_scan(e.keyCode);
    }
    else
    {
        this.code = 0;
    }
}

SpiceMsgcKeyDown.prototype =
{
    to_buffer: function(a, at)
    {
        at = at || 0;
        var dv = new SpiceDataView(a);
        dv.setUint32(at, this.code, true); at += 4;
        return at;
    },
    buffer_size: function()
    {
        return 4;
    }
}

function SpiceMsgcKeyUp(e)
{
    if (e)
    {
        this.code = keycode_to_end_scan(e.keyCode);
    }
    else
    {
        this.code = 0;
    }
}

/* Use the same functions as for KeyDown */
SpiceMsgcKeyUp.prototype.to_buffer = SpiceMsgcKeyDown.prototype.to_buffer;
SpiceMsgcKeyUp.prototype.buffer_size = SpiceMsgcKeyDown.prototype.buffer_size;

function SpiceMsgDisplayStreamCreate(a, at)
{
    this.from_buffer(a, at);
}

SpiceMsgDisplayStreamCreate.prototype =
{
    from_buffer: function(a, at)
    {
        at = at || 0;
        var dv = new SpiceDataView(a);
        this.surface_id = dv.getUint32(at, true); at += 4;
        this.id = dv.getUint32(at, true); at += 4;
        this.flags = dv.getUint8(at, true); at += 1;
        this.codec_type = dv.getUint8(at, true); at += 1;
        this.stamp = dv.getUint64(at, true); at += 8;
        this.stream_width = dv.getUint32(at, true); at += 4;
        this.stream_height = dv.getUint32(at, true); at += 4;
        this.src_width = dv.getUint32(at, true); at += 4;
        this.src_height = dv.getUint32(at, true); at += 4;

        this.dest = new SpiceRect;
        at = this.dest.from_dv(dv, at, a);
        this.clip = new SpiceClip;
        this.clip.from_dv(dv, at, a);
    },
}

function SpiceStreamDataHeader(a, at)
{
}

SpiceStreamDataHeader.prototype =
{
    from_dv : function(dv, at, mb)
    {
        this.id = dv.getUint32(at, true); at += 4;
        this.multi_media_time = dv.getUint32(at, true); at += 4;
        return at;
    },
}

function SpiceMsgDisplayStreamData(a, at)
{
    this.from_buffer(a, at);
}

SpiceMsgDisplayStreamData.prototype =
{
    from_buffer: function(a, at)
    {
        at = at || 0;
        var dv = new SpiceDataView(a);
        this.base = new SpiceStreamDataHeader;
        at = this.base.from_dv(dv, at, a);
        this.data_size = dv.getUint32(at, true); at += 4;
        this.data = dv.u8.subarray(at, at + this.data_size);
    },
}

function SpiceMsgDisplayStreamDataSized(a, at)
{
    this.from_buffer(a, at);
}

SpiceMsgDisplayStreamDataSized.prototype =
{
    from_buffer: function(a, at)
    {
        at = at || 0;
        var dv = new SpiceDataView(a);
        this.base = new SpiceStreamDataHeader;
        at = this.base.from_dv(dv, at, a);
        this.width = dv.getUint32(at, true); at += 4;
        this.height = dv.getUint32(at, true); at += 4;
        this.dest = new SpiceRect;
        at = this.dest.from_dv(dv, at, a);
        this.data_size = dv.getUint32(at, true); at += 4;
        this.data = dv.u8.subarray(at, at + this.data_size);
    },
}


function SpiceMsgDisplayStreamClip(a, at)
{
    this.from_buffer(a, at);
}

SpiceMsgDisplayStreamClip.prototype =
{
    from_buffer: function(a, at)
    {
        at = at || 0;
        var dv = new SpiceDataView(a);
        this.id = dv.getUint32(at, true); at += 4;
        this.clip = new SpiceClip;
        this.clip.from_dv(dv, at, a);
    },
}

function SpiceMsgDisplayStreamDestroy(a, at)
{
    this.from_buffer(a, at);
}

SpiceMsgDisplayStreamDestroy.prototype =
{
    from_buffer: function(a, at)
    {
        at = at || 0;
        var dv = new SpiceDataView(a);
        this.id = dv.getUint32(at, true); at += 4;
    },
}

function SpiceMsgDisplayStreamActivateReport(a, at)
{
    this.from_buffer(a, at);
}

SpiceMsgDisplayStreamActivateReport.prototype =
{
    from_buffer: function(a, at)
    {
        at = at || 0;
        var dv = new SpiceDataView(a);
        this.stream_id = dv.getUint32(at, true); at += 4;
        this.unique_id = dv.getUint32(at, true); at += 4;
        this.max_window_size = dv.getUint32(at, true); at += 4;
        this.timeout_ms = dv.getUint32(at, true); at += 4;
    },
}

function SpiceMsgcDisplayStreamReport(stream_id, unique_id)
{
    this.stream_id = stream_id;
    this.unique_id = unique_id;
    this.start_frame_mm_time = 0;
    this.end_frame_mm_time = 0;
    this.num_frames = 0;
    this.num_drops = 0;
    this.last_frame_delay = 0;

    // TODO - Implement audio delay
    this.audio_delay = -1;
}

SpiceMsgcDisplayStreamReport.prototype =
{
    to_buffer: function(a, at)
    {
        at = at || 0;
        var dv = new SpiceDataView(a);
        dv.setUint32(at, this.stream_id, true); at += 4;
        dv.setUint32(at, this.unique_id, true); at += 4;
        dv.setUint32(at, this.start_frame_mm_time, true); at += 4;
        dv.setUint32(at, this.end_frame_mm_time, true); at += 4;
        dv.setUint32(at, this.num_frames, true); at += 4;
        dv.setUint32(at, this.num_drops, true); at += 4;
        dv.setUint32(at, this.last_frame_delay, true); at += 4;
        dv.setUint32(at, this.audio_delay, true); at += 4;
        return at;
    },
    buffer_size: function()
    {
        return 8 * 4;
    }
}

function SpiceMsgDisplayInvalList(a, at)
{
    this.count = 0;
    this.resources = [];
    this.from_buffer(a,at);
}

SpiceMsgDisplayInvalList.prototype =
{
    from_buffer: function (a, at)
    {
        var i;
        at = at || 0;
        var dv = new SpiceDataView(a);
        this.count = dv.getUint16(at, true); at += 2;
        for (i = 0; i < this.count; i++)
        {
            this.resources[i] = {};
            this.resources[i].type = dv.getUint8(at, true); at++;
            this.resources[i].id = dv.getUint64(at, true); at += 8;
        }
    },
}

function SpiceMsgPortInit(a, at)
{
    this.from_buffer(a,at);
};

SpiceMsgPortInit.prototype =
{
    from_buffer: function (a, at)
    {
        at = at || 0;
        var dv = new SpiceDataView(a);
        var namesize = dv.getUint32(at, true); at += 4;
        var offset = dv.getUint32(at, true); at += 4;
        this.opened = dv.getUint8(at, true); at += 1;
        this.name = a.slice(offset, offset + namesize - 1);
    }
}

export {
  SpiceLinkHeader,
  SpiceLinkMess,
  SpiceLinkReply,
  SpiceLinkAuthTicket,
  SpiceLinkAuthReply,
  SpiceMiniData,
  SpiceMsgChannels,
  SpiceMsgMainInit,
  SpiceMsgMainMouseMode,
  SpiceMsgMainAgentData,
  SpiceMsgMainAgentTokens,
  SpiceMsgSetAck,
  SpiceMsgcAckSync,
  SpiceMsgcMainMouseModeRequest,
  SpiceMsgcMainAgentStart,
  SpiceMsgcMainAgentData,
  VDAgentAnnounceCapabilities,
  VDAgentMonitorsConfig,
  VDAgentFileXferStatusMessage,
  VDAgentFileXferStartMessage,
  VDAgentFileXferDataMessage,
  SpiceMsgNotify,
  SpiceMsgcDisplayInit,
  SpiceMsgDisplayBase,
  SpiceMsgDisplayDrawCopy,
  SpiceMsgDisplayDrawFill,
  SpiceMsgDisplayCopyBits,
  SpiceMsgSurfaceCreate,
  SpiceMsgSurfaceDestroy,
  SpiceMsgInputsInit,
  SpiceMsgInputsKeyModifiers,
  SpiceMsgCursorInit,
  SpiceMsgPlaybackData,
  SpiceMsgPlaybackMode,
  SpiceMsgPlaybackStart,
  SpiceMsgCursorSet,
  SpiceMsgcMousePosition,
  SpiceMsgcMouseMotion,
  SpiceMsgcMousePress,
  SpiceMsgcMouseRelease,
  SpiceMsgcKeyDown,
  SpiceMsgcKeyUp,
  SpiceMsgDisplayStreamCreate,
  SpiceStreamDataHeader,
  SpiceMsgDisplayStreamData,
  SpiceMsgDisplayStreamDataSized,
  SpiceMsgDisplayStreamClip,
  SpiceMsgDisplayStreamDestroy,
  SpiceMsgDisplayStreamActivateReport,
  SpiceMsgcDisplayStreamReport,
  SpiceMsgDisplayInvalList,
  SpiceMsgPortInit,
};
