//Constants
/* ACK sent by remote command*/
const ACK = '!';
/* NACK sent by remote command*/
const NACK = '?';
/* Interval in ms in whic the web pings the server
to show that it is alive*/
const PING_MS = 3000;
/** Data of a PING message*/
const pingData = new PAPI_newData(PAPI_data_type.PING, 'PING=');

//Global variables
/** Keeps track of the protocols that are correctly 
initiallized*/
var ready_protocols = 0;
/** Keeps track of the protocols that are expected to 
connect in order to report the VIEW that we are connected*/
var expected_protocols = 0;
/** Used to be able to stop the "pinging" to the
server. It is only needed to set it to 0*/
var pingInterval = undefined;
/** When a new sessions starts, the server sends a token
that must be chipered during autentication*/
var authToken;
/** REcovery token used for key recovery*/
var recToken;

//Private functions

/** When a succesfull authentication is done, server sends a
sessionId that must be used to open all other protocols during
the session (so it must be kept between page loads).
This functions saves this sId to not be reseted between page
loads
@param Id received from server*/
function setSId(Id) {
  sId = Id;
  sessionStorage.setItem('sessionId', sId);
}

/** Resets the sId when a new session is started*/
function resetSId() {
  return sessionStorage.setItem('sessionId', 0);
}

/** Gets the last stored sessionId to be used in
a protocol opening*/
function getSId() {
  return sessionStorage.getItem('sessionId');
}

/** Get the url the socket must connect to*/
function PAPI_P_get_appropriate_ws_url() {
  var pcol;
  var u = document.URL;

  /*
   * We open the websocket encrypted if this page came on an
   * https:// url itself, otherwise unencrypted
   */

  if (u.substring(0, 5) == 'https') {
    pcol = 'wss://';
    u = u.substr(8);
  } else {
    pcol = 'ws://';
    if (u.substring(0, 4) == 'http') u = u.substr(7);
  }

  u = u.split('/');

  return pcol + u[0];
}

/** Constructs a remote command form a 
PAPI_newClientData object*/
function PAPI_P_constructRemoteCmd(data) {
  if (data.value == undefined) {
    console.log('Remote send *' + data.id);
    return '*' + data.id;
  } else {
    console.log('Remote send *' + data.id + data.value);
    return '*' + data.id + data.value;
  }
}

function PAPI_P_get_data_from_header(header, body) {
  var json = JSON.parse(body);

  if (header.match('STATIC')) {
    var json = PAPI_P_add_offset_JSONStatic(body);
    return new PAPI_newData(PAPI_data_type.JSON_MONI_STATIC, json);
  } else if (header.match('MONI_MEAS')) {
    var json = PAPI_P_add_offset_JSONMeas(body);
    return new PAPI_newData(PAPI_data_type.JSON_MONI_MEAS, json);
  } else if (header.match('ALARM')) {
    return new PAPI_newData(PAPI_data_type.JSON_MONI_ALARM, json);
  } else if (header.match('CONFIG')) {
    return new PAPI_newData(PAPI_data_type.JSON_CONFIG, json);
  } else if (header.match('SP')) {
    return new PAPI_newData(PAPI_data_type.JSON_SP, json);
  } else {
    if (header != '') {
      //Log to catch the commands received
      console.log('Received ' + header);
      //If we expect a remote command answer (!= JSON_SOMETHING) it must be handled here
      return new PAPI_newData(PAPI_data_type.REMOTE_JSON, json);
    }
  }
}

/** Adds field offset to the fields in JSON STATIC that
contain a linear scale. It is so in order to allow the
view to print graphs with scales over 0 (despite labels
show the real negative values).
*/
function PAPI_P_add_offset_JSONStatic(data) {
  var json = JSON.parse(data.replace('STATIC', ''));

  if (json.hasOwnProperty('meas_opt')) {
    var meas_opt = json.meas_opt;
    for (var ii = 0; ii < meas_opt.length; ii++) {
      if (meas_opt[ii].hasOwnProperty('scale_lin')) {
        var meas = meas_opt[ii];
        var value = parseInt(meas.scale_lin[0]);
        var offset = -Math.min(0, value);
        meas.offset = offset;
      }
    }
  }

  return json;
}

/**
 *
 * same than above
 */
function PAPI_P_add_offset_JSONMeas(data) {
  var json = JSON.parse(data.replace('MEAS', ''));

  if (json.hasOwnProperty('meas_opt')) {
    var meas_opt = json.meas_opt;
    for (var ii = 0; ii < meas_opt.length; ii++) {
      if (meas_opt[ii].hasOwnProperty('scale_lin')) {
        var meas = meas_opt[ii];
        var value = parseInt(meas.scale_lin[0]);
        var offset = -Math.min(0, value);
        meas.offset = offset;
      }
    }
  }

  return json;
}

/** Keeps all the possible socket callback functions.
All methods inside here are called by a socket, 
so "this." refers to a socket*/
var PAPI_P_eventHandler = {
  /** Callback of the AUTH protocol for a new
	session opening*/
  onSessionOpen: function (message) {
    //console.log("Opening socket " + this.protocol);

    /* Send INIT command to get the token we 
		must authenticate with*/
    this.send('INIT=');
  },

  /** Callback of any protocol for a new
	protocol connection*/
  onProtocolOpen: function () {
    //Send INIT command before anything can happen
    //console.log("Remote send *INIT " + this.protocol);
    this.send('INIT=' + getSId());
  },

  /* Callback for any protocol as the answer of
	the INIT command.*/
  onINITResponse: function (message) {
    this.notifyPAPIConnectionOpen(this);

    //Set future callbacks to the corresponding function
    this.onmessage = this.onAuthCommunication;

    /** We can only receive an ACK or token as first command,
		NACK is not send here, a disconnection is done in stead from the server side*/
    if (message.data == ACK) {
      //console.log("Init response received " + this.protocol);
    } else if (message.data.length > 1) {
      //We received a token
      //console.log("Token received " + this.protocol);
      authToken = message.data;
    }

    if (this.protocol == 'AUTH') {
      //MAY: I added this condition because I realised that it is only used in session opening
      //Callback seted by the VIEW
      this.webCallbacks.onConnectionOpen();
    }
  },

  //SP protocol callback for new messages
  onNewMessage_SP: function (message) {
    var obj = JSON.parse(message.data);
    this.webCallbacks.onNewData(new PAPI_newData(PAPI_data_type.JSON_SP, obj));
  },

  //CONFIG protocol callback for new messages
  onNewMessage_CONFIG: function (message) {
    var obj = JSON.parse(message.data);
    //Callback seted by the VIEW
    this.webCallbacks.onNewData(new PAPI_newData(PAPI_data_type.JSON_CONFIG, obj));
  },

  //INFO protocol callback for new messages
  onNewMessage_INFO: function (message) {
    // console.log("Received INFO data time:");
    //DO SOMETHING WITH INFO
    var obj = JSON.parse(message.data);
    this.webCallbacks.onNewData(new PAPI_newData(PAPI_data_type.JSON_INFO, obj));
  },

  //MONI protocol callback for new messages
  onNewMessage_MONI: function (message) {
    try {
      if (message.data.match('STATIC')) {
        var json = PAPI_P_add_offset_JSONStatic(message.data);
        //Callback seted by the VIEW
        this.webCallbacks.onNewData(new PAPI_newData(PAPI_data_type.JSON_MONI_STATIC, json));
      } else if (message.data.match('MEAS')) {
        var json = PAPI_P_add_offset_JSONMeas(message.data);
        //Callback seted by the VIEW
        this.webCallbacks.onNewData(new PAPI_newData(PAPI_data_type.JSON_MONI_MEAS, json));
      } else if (message.data.match('ALARM')) {
        var obj = JSON.parse(message.data.replace('ALARM', ''));
        //Callback seted by the VIEW
        this.webCallbacks.onNewData(new PAPI_newData(PAPI_data_type.JSON_MONI_ALARM, obj));
      } else if (message.data.match('TIME')) {
        var obj = JSON.parse(message.data.replace('TIME', ''));
        //Callback seted by the VIEW
        this.webCallbacks.onNewData(new PAPI_newData(PAPI_data_type.JSON_MONI_TIME, obj));
      }
    } catch (e) {
      //If JSON isn't valid it's because monitoring is not started
    }
  },

  //SPMONI protocol callback for new messages
  onNewMessage_SPMONI: function (message) {
    try {
      if (message.data.match('SCAN')) {
        var obj = JSON.parse(message.data.replace('SCAN', ''));
        //Callback seted by the VIEW
        this.webCallbacks.onNewData(new PAPI_newData(PAPI_data_type.JSON_SPMONI, obj));
      } else if (message.data.match('REFERENCE')) {
        var obj = JSON.parse(message.data.replace('REFERENCE', ''));
        //Callback seted by the VIEW
        this.webCallbacks.onNewData(new PAPI_newData(PAPI_data_type.JSON_SPMONI_REFERENCE, obj));
      }
    } catch (e) {
      //If JSON isn't valid it's because monitoring is not started
    }
  },

  //CONFIG protocol callback for new messages
  onNewMessage_REMOTE: function (message) {
    if (message.data == NACK) {
      //TODO: Improve NACK behaviour
      console.log('Remote ERR!!');
      //Callback seted by the VIEW
      this.webCallbacks.onError(new PAPI_error(PAPI_error_reasons.CMD, message.data));
    } else if (message.data == ACK) {
      this.webCallbacks.onACK();
    } else {
      // if(message.data.match('SCAN')== null){
      var n = message.data.indexOf('{');
      var header = message.data.substring(0, n);
      var body = message.data.substring(n);

      //Any message may arrive via remote protocol, so classify them
      var data = PAPI_P_get_data_from_header(header, body);
      if (data != undefined) {
        this.webCallbacks.onNewData(data);
      }
      // }
    }
  },

  //IMG protocol callback for new messages
  onNewMessage_IMG: function (message) {
    //Callback seted by the VIEW
    this.webCallbacks.onNewData(new PAPI_newData(PAPI_data_type.IMG, message.data));
  },

  /* AUTH protocol callback for new messages
	when we are initiating a new session*/
  onNewMessage_Auth: function (message) {
    if (message.data == NACK) {
      //Callback seted by the VIEW
      this.webCallbacks.onError(new PAPI_error(PAPI_error_reasons.AUTH, PAPI_error_messages.PWD_WRONG));
    } else {
      //TODO: Find better way to distinguish JSON AUTH from authtenticate response
      if (message.data.search('REC_TOKEN=') != -1) {
        var n = message.data.indexOf('=');
        recToken = message.data.substring(n + 1, message.data.length);
      } else if (message.data.length > 10) {
        obj = JSON.parse(message.data);
        //Callback seted by the VIEW
        this.webCallbacks.onNewData(new PAPI_newData(PAPI_data_type.AUTH, obj));
      } else {
        //It isn't JSON, so it was sID
        setSId(message.data);
        //Callback seted by the VIEW
        this.webCallbacks.onAuth();
        return;
      }
    }
  },

  /* AUTH protocol callback for new messages
	when we are in the already opened session
	and changing pwd*/
  onNewMessage_PWD: function (message) {
    if (message.data == NACK) {
      //Callback seted by the VIEW
      this.webCallbacks.onError(new PAPI_error(PAPI_error_reasons.CMD, PAPI_error_messages.PWD_WRONG));
      //TODO: Find better way to distinguish JSON AUTH from authtenticate response
    } else if (message.data.length > 10) {
      obj = JSON.parse(message.data);
      //Callback seted by the VIEW
      this.webCallbacks.onNewData(new PAPI_newData(PAPI_data_type.AUTH, obj));
    } else {
      //Callback seted by the VIEW
      this.webCallbacks.onPwdChanged();
    }
  },

  /** Callback for any protocol when it closes*/
  onConnectionClose: function (e) {
    var reload = false;
    var ask = false;

    //Check if it is the last protocol we have opened
    if (this.notifyPAPIConnectionClose(this) == 0) {
      if (e.code >= 4000 && e.code < 4100) {
        /* This is a propietary reason thrown by the server,
				so there is a close msg that the user must see*/
        if (e.reason != undefined && e.reason != '') {
          ask = true;
        }
      } else if (e.code < 4000) {
        /* This is a proper close, not itended, so
				try to reconnect since it will be probably a 
				connection problem*/
        reload = true;
        this.webCallbacks.onError(new PAPI_error(PAPI_error_reasons.CONNECTION, PAPI_error_messages.DEVICE));
      }
    }
    console.log(this.protocol + ' ' + e.code + ' ' + e.reason);

    //Callback seted by the VIEW
    this.webCallbacks.onConnectionClose(e, new PAPI_reconnect(reload, ask));
  },
};

/** All the methods here must be called from a callback Object
from the previous API_P_eventHandler class*/
function PAPI_webCallbacks() {
  //Callback to be registered by the VIEW
  this.onConnectionOpen;
  this.onConnectionClose;
  this.onConnectionError;
  this.onError;
  this.onNewData;
  this.onACK;
  this.onAuth;
  this.onAuthError;
  this.onPwdChanged;
}

/* Interface for the public PAPI_handler to manage
all the actions that the VIEW may need*/
function PAPI_P_Handler() {
  /*************************************************************************
   **************** PRIVATE VARIABLES AND METHODS ***************************
   *************************************************************************/

  /** Object that keeps all possible socket callbacks*/
  var eventHandler = Object.create(PAPI_P_eventHandler);
  /** Object that will keep all callbacks set by the VIEW*/
  var webCallbacksHandler = new PAPI_webCallbacks();

  //Sockets
  var ws_AUTH;
  var ws_SP;
  var ws_CONFIG;
  var ws_REMOTE;
  var ws_IMG;
  var ws_MONI;
  var ws_SPMONI;
  var ws_INFO;

  var connected_ws = [];

  /* Variable to keep the mode in which the VIEW
	wants the equipment to be*/
  var equipmentMode;

  /** Set socket callbacks for a new session initalization*/
  var setSocket_SessionCallbacks = function (socket, authCallback) {
    socket.onopen = eventHandler.onSessionOpen;
    setSocket_Callbacks(socket, authCallback);
  };

  /** Set socket callbacks for a new protocol connection*/
  var setSocket_ProtocolCallbacks = function (socket, authCallback) {
    socket.onopen = eventHandler.onProtocolOpen;
    setSocket_Callbacks(socket, authCallback);
  };

  /** Set common socket callbacks to the given socket and
	sets the authenticaatedCommunication callback to the
	given function*/
  var setSocket_Callbacks = function (socket, authCallback) {
    socket.webCallbacks = webCallbacksHandler;
    socket.onerror = webCallbacksHandler.onConnectionError;
    socket.onclose = eventHandler.onConnectionClose;
    socket.onmessage = eventHandler.onINITResponse;
    socket.onAuthCommunication = authCallback;
    socket.notifyPAPIConnectionOpen = ws_connected;
    socket.notifyPAPIConnectionClose = ws_disconnected;
  };

  var ws_connected = function (socket) {
    connected_ws.push(socket);

    console.log('Add ' + socket.protocol);

    if (connected_ws.length == 1) {
      //In am alone, so let's ping
      pingInterval = setInterval(function () {
        if (connected_ws.length >= 1 && connected_ws[0].readyState == 1) {
          connected_ws[0].send(pingData.data);
        } else {
          clearInterval(pingInterval);
        }
      }, PING_MS);

      //From now on, if there is a connected socket, someone will be pinging
    }

    return connected_ws.length;
  };

  var ws_disconnected = function (socket) {
    var ii;
    for (ii = 0; ii < connected_ws.length; ii++) {
      if (connected_ws[ii].protocol == socket.protocol) {
        connected_ws.splice(ii, 1);
        console.log('Remove ' + socket.protocol);
      }
    }

    return connected_ws.length;
  };

  /* Open basic sockets (CONFIG, REMOTE and INFO) if 
	they aren't already opened*/
  this.openBasicSockets = function () {
    if (is_closing(ws_CONFIG) || is_closing(ws_REMOTE) || is_closing(ws_INFO)) {
      //If it is closing, reopen it after close
      window.setTimeout(openBasicSockets, 100);
      return;
    }

    if (ws_CONFIG == undefined || ws_CONFIG.readyState == 3) {
      ws_CONFIG = new WebSocket(PAPI_P_get_appropriate_ws_url(), 'CONFIG');
      setSocket_ProtocolCallbacks(ws_CONFIG, eventHandler.onNewMessage_CONFIG);
    }

    if (ws_REMOTE == undefined || ws_REMOTE.readyState == 3) {
      ws_REMOTE = new WebSocket(PAPI_P_get_appropriate_ws_url(), 'REMOTE');
      setSocket_ProtocolCallbacks(ws_REMOTE, eventHandler.onNewMessage_REMOTE);
    }

    if (ws_INFO == undefined || ws_INFO.readyState == 3) {
      ws_INFO = new WebSocket(PAPI_P_get_appropriate_ws_url(), 'INFO');
      setSocket_ProtocolCallbacks(ws_INFO, eventHandler.onNewMessage_INFO);
    }
  };

  /** Opens connection for a tab that only needs the remote socket working*/
  var connect_remote_socket_tab = function () {
    if (is_closing(ws_REMOTE) || is_closing(ws_INFO)) {
      //If it is closing, reopen it after close
      window.setTimeout(this.onEMU(), 100);
      return;
    }

    if (ws_REMOTE == undefined || ws_REMOTE.readyState == 3) {
      ws_REMOTE = new WebSocket(PAPI_P_get_appropriate_ws_url(), 'REMOTE');
      setSocket_ProtocolCallbacks(ws_REMOTE, eventHandler.onNewMessage_REMOTE);
    }

    if (ws_INFO == undefined || ws_INFO.readyState == 3) {
      ws_INFO = new WebSocket(PAPI_P_get_appropriate_ws_url(), 'INFO');
      setSocket_ProtocolCallbacks(ws_INFO, eventHandler.onNewMessage_INFO);
    }

    /*We close sockets after opening the others because we avoid
		 having the server with all sockets closed, which will close
		 the connection with Perseu (harmless but inefficent)*/
    close(ws_IMG);
    close(ws_SP);
    // close(ws_AUTH);
    close(ws_CONFIG);
    close(ws_MONI);
    close(ws_SPMONI);
  };

  /** Close the given socket with the given reason and
	message
	@note the default reason wil be that we are closing
	the socket because we want to change tab*/
  var close = function (socket, reason, message) {
    if (socket != undefined && socket.readyState == 1) {
      if (reason == undefined) {
        reason = PAPI_closeReasons.TAB_LOAD;
        message = PAPI_closeMsg.TAB_LOAD;
      }
      socket.close(reason, message + socket.protocol);
    }
  };

  /* Close basic sockets (CONFIG and REMOTE)*/
  var closeBasicSockets = function (reason, message) {
    close(ws_CONFIG, reason, message);
    // close(ws_REMOTE, reason, message);
    // close(ws_INFO, reason, message);
  };

  /* Chekc if the given socket is in closing state*/
  var is_closing = function (socket) {
    return socket != undefined && socket.readyState == 2;
  };

  /* Send a remote command to switch the
	equipment mode*/
  var switchEquipmentMode = function () {
    if (ws_REMOTE == undefined || ws_REMOTE.readyState != 1) {
      window.setTimeout(switchEquipmentMode, 100);
    } else {
      console.log('Remote send *MODE ' + equipmentMode);
      ws_REMOTE.send('*MODE ' + equipmentMode);
    }
  };

  /* Switches the mode of the equipmen if possible or 
	retries it until it suceeds*/
  var switchMode = function (mode) {
    equipmentMode = mode;
    switchEquipmentMode();
  };

  /* Hases the given password*/
  var getHashedPwd = function (pwd) {
    return CryptoJS.SHA256(pwd);
  };

  /** Encrypts the token that we received on the session Init
	with the password entered by the user*/
  var getEncryptedToken = function (pwd) {
    /* Docu: https://code.google.com/archive/p/crypto-js/#AES*/

    var hashed_pwd = getHashedPwd(pwd);
    var key = CryptoJS.enc.Hex.parse(hashed_pwd.toString());
    var iv = CryptoJS.enc.Hex.parse('0000000000000000000000000000000000000000000000000000000000000000');

    return CryptoJS.AES.encrypt(authToken, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.ZeroPadding });
  };

  /*************************************************************************
   ******************** PUBLIC METHODS **************************************
   *************************************************************************/

  //CALLBACKS
  /** Registration of the VIEW callabacks*/
  this.registerEvent_onConnectionOpen = function (func) {
    webCallbacksHandler.onConnectionOpen = func;
  };
  this.registerEvent_onConnectionClose = function (func) {
    webCallbacksHandler.onConnectionClose = func;
  };
  this.registerEvent_onConnectionError = function (func) {
    webCallbacksHandler.onConnectionError = func;
  };
  this.registerEvent_onError = function (func) {
    webCallbacksHandler.onError = func;
  };
  this.registerEvent_onNewData = function (func) {
    webCallbacksHandler.onNewData = func;
  };
  this.registerEvent_onACK = function (func) {
    webCallbacksHandler.onACK = func;
  };
  this.registerEvent_onAuth = function (func) {
    webCallbacksHandler.onAuth = func;
  };
  this.registerEvent_onPwdChanged = function (func) {
    webCallbacksHandler.onPwdChanged = func;
  };

  //CONNECTIONS
  /** Handles the sockets dis/connection
	for the SP tab*/
  this.onSP = function () {
    if (is_closing(ws_SP)) {
      //If it is closing, reopen it after close
      window.setTimeout(onSP, 100);
      return;
    }

    switchMode('PREV');

    this.openBasicSockets();

    if (ws_SP == undefined || ws_SP.readyState == 3) {
      ws_SP = new WebSocket(PAPI_P_get_appropriate_ws_url(), 'SP');
      setSocket_ProtocolCallbacks(ws_SP, eventHandler.onNewMessage_SP);
    }

    /*We close sockets after opening the others because we avoid
		having the server with all sockets closed, which will close
		the connection with Perseu (harmless but inefficent)*/
    close(ws_IMG);
    // close(ws_AUTH);
    close(ws_MONI);
    close(ws_SPMONI);
  };

  /** Handles the sockets dis/connection
	for the TV tab*/
  this.onTV = function () {
    if (is_closing(ws_IMG)) {
      //If it is closing, reopen it after close
      window.setTimeout(this.onTV(), 100);
      return;
    }

    switchMode('TV+SP+MEASURE');

    this.openBasicSockets();

    if (ws_IMG == undefined || ws_IMG.readyState == 3) {
      ws_IMG = new WebSocket(PAPI_P_get_appropriate_ws_url(), 'WEB_IMG');
      setSocket_ProtocolCallbacks(ws_IMG, eventHandler.onNewMessage_IMG);
    }

    /*We close sockets after opening the others because we avoid
		having the server with all sockets closed, which will close
		the connection with Perseu (harmless but inefficent)*/
    close(ws_SP);
    // close(ws_AUTH);
    close(ws_MONI);
    close(ws_SPMONI);
  };

  /** Handles the sockets dis/connection
	for the img show button*/
  this.onIMG = function () {
    if (is_closing(ws_IMG)) {
      //If it is closing, reopen it after close
      window.setTimeout(this.onIMG(), 100);
      return;
    }

    switchMode('TV+SP+MEASURE');

    this.openBasicSockets();

    if (ws_IMG == undefined || ws_IMG.readyState == 3) {
      ws_IMG = new WebSocket(PAPI_P_get_appropriate_ws_url(), 'WEB_IMG');
      setSocket_ProtocolCallbacks(ws_IMG, eventHandler.onNewMessage_IMG);
    }
  };
  /** Handles the sockets dis/connection
	for the img hide button*/
  this.onStopIMG = function () {
    close(ws_IMG);
  };

  this.onEMU = function () {
    connect_remote_socket_tab();
  };

  this.onMONI = function (monType) {
    //this.openBasicSockets();

    if (monType == 'MON') {
      if (is_closing(ws_MONI)) {
        //If it is closing, reopen it after close
        window.setTimeout(this.onMONI(monType), 100);
        return;
      }

      if (ws_MONI == undefined || ws_MONI.readyState == 3) {
        ws_MONI = new WebSocket(PAPI_P_get_appropriate_ws_url(), 'MONI');
        setSocket_ProtocolCallbacks(ws_MONI, eventHandler.onNewMessage_MONI);
      }

      close(ws_SPMONI);
    }

    if (monType == 'SPMON') {
      if (is_closing(ws_SPMONI)) {
        //If it is closing, reopen it after close
        window.setTimeout(this.onMONI(monType), 100);
        return;
      }

      if (ws_SPMONI == undefined || ws_SPMONI.readyState == 3) {
        ws_SPMONI = new WebSocket(PAPI_P_get_appropriate_ws_url(), 'SCAN');
        setSocket_ProtocolCallbacks(ws_SPMONI, eventHandler.onNewMessage_SPMONI);
      }

      close(ws_MONI);
    }

    // if(ws_REMOTE == undefined || ws_REMOTE.readyState == 3 ) {
    // 	ws_REMOTE = new WebSocket(PAPI_P_get_appropriate_ws_url(), 'REMOTE');
    // 	setSocket_ProtocolCallbacks(ws_REMOTE, eventHandler.onNewMessage_REMOTE);
    // }

    /*We close sockets after opening the others because we avoid
		having the server with all sockets closed, which will close
		the connection with Perseu (harmless but inefficent)*/
    close(ws_IMG);
    close(ws_SP);
    // close(ws_AUTH);
    // close(ws_CONFIG);
  };

  this.onHIST = function () {
    connect_remote_socket_tab();
  };

  this.onINST = function () {
    connect_remote_socket_tab();
  };

  /** Handles the sockets dis/connection
	for the LOGIN page*/
  this.onNewSession = function () {
    if (is_closing(ws_AUTH)) {
      //If it is closing, reopen it after close
      window.setTimeout(this.onNewSession(), 100);
      return;
    }

    window.WebSocket = window.WebSocket || window.MozWebSocket;

    close(ws_SP);
    close(ws_IMG);
    close(ws_MONI);
    close(ws_SPMONI);
    closeBasicSockets();

    if (ws_AUTH == undefined || ws_AUTH.readyState == 3) {
      ws_AUTH = new WebSocket(PAPI_P_get_appropriate_ws_url(), 'AUTH');
      setSocket_SessionCallbacks(ws_AUTH, eventHandler.onNewMessage_Auth);
    }
  };

  /** Handles the sockets dis/connection
	for the NEWPWD tab*/
  this.onNewPwd = function () {
    if (is_closing(ws_AUTH)) {
      //If it is closing, reopen it after close
      window.setTimeout(this.onNewPwd(), 100);
      return;
    }

    if (ws_AUTH == undefined || ws_AUTH.readyState == 3) {
      ws_AUTH = new WebSocket(PAPI_P_get_appropriate_ws_url(), 'AUTH');
      setSocket_ProtocolCallbacks(ws_AUTH, eventHandler.onNewMessage_PWD);
    } else {
      ws_AUTH.onmessage = eventHandler.onNewMessage_PWD;
    }

    /*We close sockets after opening the others because we avoid
		having the server with all sockets closed, which will close
		the connection with Perseu (harmless but inefficent)*/
    close(ws_SP);
    close(ws_IMG);
    close(ws_MONI);
    close(ws_SPMONI);
    closeBasicSockets();
  };

  /** Closes all sockets with LOGOUT reason*/
  this.closeAll = function () {
    closeBasicSockets(PAPI_closeReasons.LOGOUT, PAPI_closeMsg.LOGOUT);

    close(ws_SP, PAPI_closeReasons.LOGOUT, PAPI_closeMsg.LOGOUT);
    close(ws_IMG, PAPI_closeReasons.LOGOUT, PAPI_closeMsg.LOGOUT);
    close(ws_AUTH, PAPI_closeReasons.LOGOUT, PAPI_closeMsg.LOGOUT);

    clearInterval(pingInterval);
  };

  this.closeSocket = function (socket) {
    close(eval(socket));
  };

  /* JSON_AUTH, the only data that can be
	obtained without autentiation*/
  this.get_unauth_data = function () {
    if (ws_AUTH == undefined || ws_AUTH.readyState != 1) {
      webCallbacksHandler.onError(new PAPI_error(PAPI_error_reasons.CONNECTION, 'AUTH socket closed'));
      return;
    }
    // console.log("Sending JSON AUTH=");
    ws_AUTH.send('JSON AUTH=');
    ws_AUTH.send('REC_TOKEN=');
  };

  //Auth
  this.auth = function (data, recovery) {
    if (ws_AUTH == undefined || ws_AUTH.readyState != 1) {
      webCallbacksHandler.onError(new PAPI_error(PAPI_error_reasons.CONNECTION, 'No socket connected'));
      return;
    } else {
      var auth = getEncryptedToken(data.value);
      var cmd = 'AUTH=';

      if (recovery) {
        cmd = 'REC_AUTH=';
      }

      console.log('Sending ' + cmd + auth.ciphertext);
      ws_AUTH.send(cmd + auth.ciphertext);
    }
  };

  //Change pwd
  this.changePwd = function (data, recovery) {
    if (ws_AUTH == undefined || ws_AUTH.readyState != 1) {
      webCallbacksHandler.onError(new PAPI_error(PAPI_error_reasons.CONNECTION, 'AUTH socket closed'));
      return;
    }
    var auth = getEncryptedToken(data.id);

    var pwd = getHashedPwd(data.value);

    var cmd = 'PWD=';

    if (recovery) {
      cmd = 'REC_PWD=';
    }

    ws_AUTH.send(cmd + auth.ciphertext + 'NEW PWD=' + pwd);
  };

  //Get the code that should be entered to the promax app to recover the pwd
  this.get_pwd_recovery_code = function () {
    return recToken.substring(0, 32);
  };

  //Send data to the Server (expecting an object of type PL_newClientData)
  this.send = function (data) {
    if (data.type == PAPI_data_type.CMD) {
      if (ws_REMOTE == undefined || ws_REMOTE.readyState != 1) {
        webCallbacksHandler.onError(new PAPI_error(PAPI_error_reasons.CONNECTION, 'REMOTE socket closed'));
        return;
      }
      ws_REMOTE.send(PAPI_P_constructRemoteCmd(data));
    }
  };

  /** Starts streaming until it succeeds */
  this.startStreaming = function () {
    if (ws_REMOTE == undefined || ws_REMOTE.readyState != 1) {
      window.setTimeout(tstartStreaming, 100);
    } else {
      console.log('Starting streaming');
      // ws_REMOTE.send("*WEBCTRL EMU ON");
      ws_REMOTE.send('*?WEBCTRL EMU ON');
      ws_REMOTE.send('*STREAMING ACTION=START TYPE=SCREEN-TCP-CLI IP=127.0.0.1 PORT=9090');
    }
  };

  this.stopStreaming = function () {
    if (ws_REMOTE == undefined || ws_REMOTE.readyState != 1) {
      window.setTimeout(this.stopStreaming, 100);
    } else {
      // console.log("Stopping streaming");
      ws_REMOTE.send('*STREAMING ACTION=STOP TYPE=SCREEN-TCP-CLI');
      ws_REMOTE.send('*?WEBCTRL EMU OFF');
      // ws_REMOTE.send("*WEBCTRL EMU OFF");
    }
  };

  /** Starts audio until it succeeds */
  this.startAudio = function (obj) {
    if (ws_REMOTE == undefined || ws_REMOTE.readyState != 1) {
      window.setTimeout(this.startAudio, 100);
    } else {
      if (obj.enabled) {
        if (obj.tab == 'tv') {
          if (obj.signalType == 'FM') {
            ws_REMOTE.send('*STREAMING ACTION=START TYPE=AUDIO-WEB-FM IP=127.0.0.1 PORT=9091'); //TV MONITOR TAB(FM AUDIO)
          } else if (obj.signalType == 'DAB') {
            ws_REMOTE.send('*STREAMING ACTION=START TYPE=AUDIO-WEB-DAB IP=127.0.0.1 PORT=9091'); //TV MONITOR TAB(DAB AUDIO)
          } else {
            ws_REMOTE.send('*STREAMING ACTION=START TYPE=AUDIO-WEB-TV IP=127.0.0.1 PORT=9091'); //TV MONITOR TAB(DAB AUDIO)
          }
        } else {
          if (obj.tab == 'audio') {
            if (obj.signalType == 'FM') {
              ws_REMOTE.send('*STREAMING ACTION=START TYPE=AUDIO-FILE-FM UNIT=' + obj.memory.toUpperCase() + ' FILE=' + obj.description + '.wav'); //AUDIO RECORD TAB (FM RECORD IN FILE)
            } else if (obj.signalType == 'DAB') {
              ws_REMOTE.send('*STREAMING ACTION=START TYPE=AUDIO-FILE-DAB UNIT=' + obj.memory.toUpperCase() + ' FILE=' + obj.description + '.wav'); //AUDIO RECORD TAB (DAB RECORD IN FILE)
            } else {
              ws_REMOTE.send('*STREAMING ACTION=START TYPE=AUDIO-FILE-TV UNIT=' + obj.memory.toUpperCase() + ' FILE=' + obj.description + '.wav'); //AUDIO RECORD TAB (TV RECORD IN FILE)
            }
          }
        }
      } else {
        ws_REMOTE.send('*STREAMING ACTION=STOP');
      }
    }
  };

  this.startETIRecord = function (obj) {
    if (ws_REMOTE == undefined || ws_REMOTE.readyState != 1) {
      window.setTimeout(this.startAudio, 100);
    } else {
      if (obj.enabled) {
        ws_REMOTE.send('*STREAMING ACTION=START TYPE=ETI-FILE-DAB UNIT=' + obj.memory.toUpperCase() + ' FILE=' + obj.description + '.eti'); //ETI RECORD TAB
      } else {
        ws_REMOTE.send('*STREAMING ACTION=STOP');
      }
    }
  };

  this.startStreamingTS = function (enabled, ip, port, tsStreaming) {
    if (ws_REMOTE == undefined || ws_REMOTE.readyState != 1) {
      window.setTimeout(this.startStreamingTS, 100);
    } else {
      if (enabled) {
        if (tsStreaming) {
          ws_REMOTE.send('*STREAMING ACTION=START TYPE=TS IP=' + ip + ' PORT=' + port);
        } else {
          ws_REMOTE.send('*STREAMING ACTION=START TYPE=SERVICE IP=' + ip + ' PORT=' + port);
        }
      } else {
        ws_REMOTE.send('*STREAMING ACTION=STOP TYPE=TS');
      }
    }
  };
}

//Propietary reasons of the web to close a connection
var PAPI_closeReasons = Object.freeze({
  TAB_LOAD: 4100, //Closes between tab loading
  LOGOUT: 4101, //log Out
});

/*Close messages that could be display in the console
for debuging purposes*/
var PAPI_closeMsg = Object.freeze({
  TAB_LOAD: 'close requested by',
  LOGOUT: 'LogOut by ',
});
