/**
 * gpmclient.js - support the gpm mouse protocol
 * Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License).
 * https://github.com/chjj/blessed
 */

var net = require('net');
var fs = require('fs');
var EventEmitter = require('events').EventEmitter;

var GPM_USE_MAGIC = false;

var GPM_MOVE = 1, GPM_DRAG = 2, GPM_DOWN = 4, GPM_UP = 8;

var GPM_DOUBLE = 32, GPM_MFLAG = 128;

var GPM_REQ_NOPASTE = 3, GPM_HARD = 256;

var GPM_MAGIC = 0x47706D4C;
var GPM_SOCKET = '/dev/gpmctl';

// typedef struct Gpm_Connect {
//   unsigned short eventMask, defaultMask;
//   unsigned short minMod, maxMod;
//   int pid;
//   int vc;
// } Gpm_Connect;

interface GpmConnect {
  eventMask: number;
  defaultMask: number;
  minMod: number;
  maxMod: number;
  pid: number;
  vc: number;
}

interface GpmEvent {
  buttons: number;
  modifiers: number;
  vc: number;
  dx: number;
  dy: number;
  x: number;
  y: number;
  type: number;
  clicks: number;
  margin: number;
  wdx: number;
  wdy: number;
}

interface GpmClientInterface extends EventEmitter {
  gpm?: any;
  stop(): void;
  ButtonName(btn: number): string;
  hasShiftKey(mod: number): boolean;
  hasCtrlKey(mod: number): boolean;
  hasMetaKey(mod: number): boolean;
}

function send_config(socket: any, Gpm_Connect: GpmConnect, callback?: Function): void {
  var buffer: Buffer;
  if (GPM_USE_MAGIC) {
    buffer = new Buffer(20);
    buffer.writeUInt32LE(GPM_MAGIC, 0);
    buffer.writeUInt16LE(Gpm_Connect.eventMask, 4);
    buffer.writeUInt16LE(Gpm_Connect.defaultMask, 6);
    buffer.writeUInt16LE(Gpm_Connect.minMod, 8);
    buffer.writeUInt16LE(Gpm_Connect.maxMod, 10);
    buffer.writeInt16LE(process.pid, 12);
    buffer.writeInt16LE(Gpm_Connect.vc, 16);
  } else {
    buffer = new Buffer(16);
    buffer.writeUInt16LE(Gpm_Connect.eventMask, 0);
    buffer.writeUInt16LE(Gpm_Connect.defaultMask, 2);
    buffer.writeUInt16LE(Gpm_Connect.minMod, 4);
    buffer.writeUInt16LE(Gpm_Connect.maxMod, 6);
    buffer.writeInt16LE(Gpm_Connect.pid, 8);
    buffer.writeInt16LE(Gpm_Connect.vc, 12);
  }
  socket.write(buffer, function() {
    if (callback) callback();
  });
}

// typedef struct Gpm_Event {
//   unsigned char buttons, modifiers;  // try to be a multiple of 4
//   unsigned short vc;
//   short dx, dy, x, y; // displacement x,y for this event, and absolute x,y
//   enum Gpm_Etype type;
//   // clicks e.g. double click are determined by time-based processing
//   int clicks;
//   enum Gpm_Margin margin;
//   // wdx/y: displacement of wheels in this event. Absolute values are not
//   // required, because wheel movement is typically used for scrolling
//   // or selecting fields, not for cursor positioning. The application
//   // can determine when the end of file or form is reached, and not
//   // go any further.
//   // A single mouse will use wdy, "vertical scroll" wheel.
//   short wdx, wdy;
// } Gpm_Event;

function parseEvent(raw: Buffer): GpmEvent {
  var evnt: any = {};
  evnt.buttons = raw[0];
  evnt.modifiers = raw[1];
  evnt.vc = raw.readUInt16LE(2);
  evnt.dx = raw.readInt16LE(4);
  evnt.dy = raw.readInt16LE(6);
  evnt.x = raw.readInt16LE(8);
  evnt.y = raw.readInt16LE(10);
  evnt.type = raw.readInt16LE(12);
  evnt.clicks = raw.readInt32LE(16);
  evnt.margin = raw.readInt32LE(20);
  evnt.wdx = raw.readInt16LE(24);
  evnt.wdy = raw.readInt16LE(26);
  return evnt;
}

function GpmClient(this: GpmClientInterface, options?: any): GpmClientInterface {
  if (!(this instanceof GpmClient)) {
    return new GpmClient(options);
  }

  EventEmitter.call(this);

  var pid: number = process.pid;

  // check tty for /dev/tty[n]
  var path: string | undefined;
  try {
    path = fs.readlinkSync('/proc/' + pid + '/fd/0');
  } catch (e) {
  }
  var tty = path ? /tty[0-9]+$/.exec(path) : null;
  if (tty === null) {
    // TODO: should  also check for /dev/input/..
  }

  var vc: number | undefined;
  if (tty) {
    tty = tty[0];
    vc = +/[0-9]+$/.exec(tty)[0];
  }

  var self = this;

  if (tty) {
    fs.stat(GPM_SOCKET, function(err, stat) {
      if (err || !stat.isSocket()) {
        return;
      }

      var conf: GpmConnect = {
        eventMask: 0xffff,
        defaultMask: GPM_MOVE | GPM_HARD,
        minMod: 0,
        maxMod: 0xffff,
        pid: pid,
        vc: vc
      };

      var gpm = net.createConnection(GPM_SOCKET);
      this.gpm = gpm;

      gpm.on('connect', function() {
        send_config(gpm, conf, function() {
          conf.pid = 0;
          conf.vc = GPM_REQ_NOPASTE;
          //send_config(gpm, conf);
        });
      });

      gpm.on('data', function(packet) {
        var evnt = parseEvent(packet);
        switch (evnt.type & 15) {
          case GPM_MOVE:
            if (evnt.dx || evnt.dy) {
              self.emit('move', evnt.buttons, evnt.modifiers, evnt.x, evnt.y);
            }
            if (evnt.wdx || evnt.wdy) {
              self.emit('mousewheel',
                evnt.buttons, evnt.modifiers,
                evnt.x, evnt.y, evnt.wdx, evnt.wdy);
            }
            break;
          case GPM_DRAG:
            if (evnt.dx || evnt.dy) {
              self.emit('drag', evnt.buttons, evnt.modifiers, evnt.x, evnt.y);
            }
            if (evnt.wdx || evnt.wdy) {
              self.emit('mousewheel',
                evnt.buttons, evnt.modifiers,
                evnt.x, evnt.y, evnt.wdx, evnt.wdy);
            }
            break;
          case GPM_DOWN:
            self.emit('btndown', evnt.buttons, evnt.modifiers, evnt.x, evnt.y);
            if (evnt.type & GPM_DOUBLE) {
              self.emit('dblclick', evnt.buttons, evnt.modifiers, evnt.x, evnt.y);
            }
            break;
          case GPM_UP:
            self.emit('btnup', evnt.buttons, evnt.modifiers, evnt.x, evnt.y);
            if (!(evnt.type & GPM_MFLAG)) {
              self.emit('click', evnt.buttons, evnt.modifiers, evnt.x, evnt.y);
            }
            break;
        }
      });

      gpm.on('error', function() {
        self.stop();
      });
    });
  }
}

GpmClient.prototype.__proto__ = EventEmitter.prototype;

GpmClient.prototype.stop = function(this: GpmClientInterface): void {
  if (this.gpm) {
    this.gpm.end();
  }
  delete this.gpm;
};

GpmClient.prototype.ButtonName = function(this: GpmClientInterface, btn: number): string {
  if (btn & 4) return 'left';
  if (btn & 2) return 'middle';
  if (btn & 1) return 'right';
  return '';
};

GpmClient.prototype.hasShiftKey = function(this: GpmClientInterface, mod: number): boolean {
  return (mod & 1);
};

GpmClient.prototype.hasCtrlKey = function(this: GpmClientInterface, mod: number): boolean {
  return (mod & 4);
};

GpmClient.prototype.hasMetaKey = function(this: GpmClientInterface, mod: number): boolean {
  return (mod & 8);
};

module.exports = GpmClient;
