/**
 * Created by cgarcia on 04/09/2017.
 */

var MeasParser = function (json_meas, json_static) {
  this.json_meas = json_meas.ch_meas;
  this.json_static = json_static.labels;
  this.json_static_channels = json_static.ch_def;

  /*once "buildChannelList()" method is called information of channel is saved in this variable.
    "channel_list" is an array of objects. every item in this array  contents the information of all channels with the same
    signal type (DVB-T, DVB-T2, DVB-C,...). each item includes the header and the data. there is only one header for
    signal type and content and array of objects with 3 properties:id, label and units. 
    data is an array where each item contains an object with the measures of a single channel. properties
    name corresponds with the "id" property of header items.*/

  this.channel_list = [];
  //intermediate data used to process "json_meas" information.
  this.data = [];
  //this variable keeps the measure order shown in the header.
  this.order = [
    'POWER',
    'LEVEL',
    'CN',
    'VA',
    'MER',
    'CBER',
    'VBER',
    'LBER',
    'SER',
    'MODULATIONPOWER',
    'MPXFREQUENCYDEVIATION',
    'LFREQUENCYDEVIATION',
    'RFREQUENCYDEVIATION',
    'LAUDIOLEVEL',
    'RAUDIOLEVEL',
    'FIB_Ratio',
    'MSC_CBER',
    'FIC_CBER',
  ];

  //this variable keeps the measures represented in scientific notation.
  this.scientificNotation = ['CBER', 'VBER', 'LBER', 'SER', 'MSC_CBER', 'FIC_CBER'];
  //if a measure inside the header is not found this is the text that will be
  //shown instead.//if missing_measure is null nothing is done.
  this.missing_measure = '--';
  //num of header items in the channel_list[].header.
  this.num_header_items = 6;
};

//parse the json measure and json static information and build the channel_list
//variable.
MeasParser.prototype.buildChannelList = function () {
  //clear previous information.
  this.channel_list = [];
  this.data = [];

  //build a string with the information of signal (first item of header
  //array). beside channel includes all properties that have meas property
  //(profile, plp, dslice...)
  function returnSignalInfoTxt(static, node, index) {
    function getLabel(static, id) {
      for (var ii = 0; ii < static.length; ii++) {
        var node = static[ii];
        if (node.hasOwnProperty('id') && node.id == id) {
          return node.label;
        }
      }
      return id;
    }

    var txt = node.data[index][node.header[0][0]];
    for (var ii = 1; ii < node.header[0].length; ii++) {
      var property = getLabel(static, node.header[0][ii]);
      var value = node.data[index][node.header[0][ii]];
      if (value != undefined) {
        txt += ' | ' + value;
      }
    }
    return txt;
  }

  //build an object that includes the label of signal_id provided.
  function returnStaticSignalId(static, signal_id) {
    for (var ii = 0; ii < static.length; ii++) {
      var node = static[ii];
      if (node.id == signal_id) {
        return { id: 'SIGNAL', label: node.id, units: '' };
      }
    }
  }

  //build an object that includes the id, label and unit of property id.
  function returnStaticInfoId(static, id) {
    for (var ii = 0; ii < static.length; ii++) {
      var node = static[ii];
      if (node.hasOwnProperty('id') && node.id == id) {
        var item = { id: node.id, label: node.label };
        if (node.hasOwnProperty('units')) {
          item['units'] = '(' + node.units + ')';
        } else {
          item['units'] = '';
        }
        return item;
      }
    }
  }

  //build intermediate information based only with json_meas and store it in data"
  this.buildInfo();
  //fill missing measures
  if (this.missing_measure != null) {
    this.fillMissingMeasures(this.missing_measure);
  }
  //link static information with "data" information.
  for (var ii = 0; ii < this.data.length; ii++) {
    var node_header = this.data[ii].header;
    var new_header = [];
    new_header.push(returnStaticSignalId(this.json_static, node_header[0][0]));
    for (var jj = 1; jj < node_header.length; jj++) {
      new_header.push(returnStaticInfoId(this.json_static, node_header[jj]));
    }
    //fill with empty headers until reach the "num_header_items" value.
    for (var jj = new_header.length; jj < this.num_header_items; jj++) {
      new_header.push({ id: jj + 1, label: '' });
    }
    for (var jj = 0; jj < this.data[ii].data.length; jj++) {
      this.data[ii].data[jj]['SIGNAL'] = returnSignalInfoTxt(this.json_static, this.data[ii], jj);
    }
    this.channel_list.push({ header: new_header, data: this.data[ii].data });
  }
  return this.channel_list;
};

MeasParser.prototype.getChannelFrequency = function (channel) {
  var frequency = 0;
  var table = this.json_static_channels;

  if (table != undefined) {
    for (var ii = 0; ii < table.length; ++ii) {
      if (table[ii].hasOwnProperty('channel_id') && table[ii].channel_id == channel && table[ii].hasOwnProperty('Freq')) {
        frequency = Number(table[ii].Freq);
        break;
      }
    }
  }

  return frequency;
};

MeasParser.prototype.addFrequency = function () {
  for (var ii = 0; ii < this.data.length; ii++) {
    var signal_table = this.data[ii];
    var signal_property = this.data[ii].header[0][0];
    for (var jj = 0; jj < signal_table.data.length; jj++) {
      var channel_table = signal_table.data[jj];
      if (channel_table.hasOwnProperty(signal_property)) {
        var channel = channel_table[signal_property];
        var frequency = this.getChannelFrequency(channel);
        if (frequency != 0) {
          channel_table['FREQ'] = frequency;
        }
      }
    }
  }
};

MeasParser.prototype.buildInfo = function () {
  //find the node with "channel_id" poperty and return its "id" property.
  function getSignalTypeInMeas(channel) {
    return channel.id;
  }

  //iterate the channel and build the header information. header information
  //includes signal information (channel_id, plp, profile, dslice...) and
  //measures specified in the order variable.
  function buildHeader(channel, order, oldHeader) {
    var header = [channel.id];
    header[0] = [];

    //iterate over object node (node belongs to json_meas variable) and create an
    //array in the first item of header array with ids that have a "meas" property.
    //Used to keep plps, profiles, layers...
    function updateSignalProperties(node) {
      //find index of first object with "meas" property.
      for (var ii = 0; ii < node.length; ii++) {
        if (node[ii].hasOwnProperty('meas')) {
          var already_present = false;

          //Check if we already have this property
          if (oldHeader != undefined) {
            for (var jj = 0; jj < oldHeader.length; jj++) {
              if (oldHeader[0][jj] == node[ii].id) {
                already_present = true;
                break;
              }
            }
          }

          if (already_present == false) {
            header[0].push(node[ii].id);
            updateSignalProperties(node[ii].meas);
            break;
          }
        }
      }
    }

    //iterate over object node (node belongs to json_meas variable) and
    //if found the id, it's include in the header array.
    //Used to add measures to the header array
    function addIfFound(node, id) {
      for (var ii = 0; ii < node.length; ii++) {
        if (node[ii].id == id) {
          var already_present = false;

          //Check if we already have this measure
          if (oldHeader != undefined) {
            for (var jj = 1; jj < oldHeader.length; jj++) {
              if (oldHeader[jj] == id) {
                already_present = true;
                break;
              }
            }
          }

          if (already_present == false) {
            header.push(id);
            return true;
          }
        } else {
          if (node[ii].hasOwnProperty('meas')) {
            if (addIfFound(node[ii].meas, id)) {
              return true;
            }
          }
        }
      }
      return false;
    }

    function addMeasure(id) {
      addIfFound(channel.meas, id);
    }

    if (oldHeader == undefined) {
      header[0].push(channel.id);
    } else {
      header = oldHeader;
    }

    if (channel.hasOwnProperty('meas')) {
      updateSignalProperties(channel.meas);
      order.forEach(addMeasure);
    }

    return header;
  }

  if (this.json_meas != undefined) {
    //iterate over all channels and build the headers.
    for (var ii = 0; ii < this.json_meas.length; ii++) {
      var signal_id = getSignalTypeInMeas(this.json_meas[ii]);
      var oldHeader = this.getFullHeaderFromTable(signal_id);
      var header = { header: buildHeader(this.json_meas[ii], this.order, oldHeader) };
      var index = this.findIndexOfSignalInTable(signal_id);
      if (index == null) {
        this.data.push(header);
      } else {
        this.data[index] = header;
      }
    }
    //iterate over all channels and build the data.
    for (var ii = 0; ii < this.json_meas.length; ii++) {
      var index = this.findIndexOfSignalInTable(getSignalTypeInMeas(this.json_meas[ii]));
      this.addSignalInDataTable(this.json_meas[ii], this.data[index]);
    }

    //iterate over all virtual-channels and add the frequency channel.
    this.addFrequency();
  }
};

//check that all measures inside the header array are present in the
//data array. if a measure is not found is added with the text pass
//in "missing_txt" variable.
MeasParser.prototype.fillMissingMeasures = function (missing_txt) {
  //loop to iterate over all signal_type found in this.data
  for (var ii = 0; ii < this.data.length; ii++) {
    var signal_it = this.data[ii];
    //loop to iterate over all id of this signal_type.
    for (var jj = 1; jj < signal_it.header.length; jj++) {
      var header_it = signal_it.header[jj];
      //loop to iterate over all channel of this signal_type
      for (var kk = 0; kk < signal_it.data.length; kk++) {
        var channel_it = signal_it.data[kk];
        if (channel_it.hasOwnProperty(header_it) == false) {
          channel_it[header_it] = missing_txt;
        }
      }
    }
  }
};

//add all virtual channels (profiles, plp...) of a channel.
MeasParser.prototype.addSignalInDataTable = function (meas_channel, meas_data) {
  var numSubChannels = this.addSignalInfoInDataTable(meas_channel, meas_data);
  for (var ii = numSubChannels; ii > 0; ii--) {
    this.addMeasuresInDataTable(meas_channel, meas_data.header, meas_data.data[meas_data.data.length - ii]);
  }
};

//add the signal properties of all virtual channels (profiles, plp...) of a channel.
MeasParser.prototype.addSignalInfoInDataTable = function (meas_r, table_r) {
  var counter = 0;
  function insertChannel(channel, table_r, it) {
    var clone_channel = JSON.parse(JSON.stringify(channel));
    var end_tree = true;
    //search for a object with "meas" property.
    for (var ii = 0; ii < it.length; ii++) {
      if (it[ii].hasOwnProperty('meas')) {
        clone_channel[it[ii].id] = it[ii].value;
        end_tree = false;
        insertChannel(clone_channel, table_r, it[ii].meas);
      }
    }
    // no mode nodes inside this branch. end of iteration.
    // insert the channel data.
    if (end_tree) {
      counter += 1;
      table_r.data.push(clone_channel);
    }
    return counter;
  }

  var it = meas_r;
  var channel = {};

  if (table_r.hasOwnProperty('data') == false) {
    table_r.data = [];
  }

  //get the "channel_id" property.
  //this is the information related to signal type and channel name.
  channel[it.id] = it.channel_id;

  if (it.hasOwnProperty('meas')) {
    return insertChannel(channel, table_r, it.meas);
  } else {
    return insertChannel(channel, table_r, it);
  }
};

// return true if the header of "signal_id" is already built
MeasParser.prototype.findSignalTypeInTable = function (signal_id) {
  for (var ii = 0; ii < this.data.length; ii++) {
    if (this.data[ii].header[0][0] == signal_id) {
      return true;
    }
  }
  return false;
};

// return true if the header with some measure of "signal_id" is already built
MeasParser.prototype.getFullHeaderFromTable = function (signal_id) {
  for (var ii = 0; ii < this.data.length; ii++) {
    if (this.data[ii].header[0][0] == signal_id) {
      //if (this.data[ii].header.length != 1) {
      return this.data[ii].header;
      //}
    }
  }
};

// return the index of signal_id specified inside the data table.
MeasParser.prototype.findIndexOfSignalInTable = function (signal_id) {
  for (var ii = 0; ii < this.data.length; ii++) {
    if (this.data[ii].header[0][0] == signal_id) {
      return ii;
    }
  }
  return null;
};

MeasParser.prototype.format = function (id, value) {
  // split the value to get the range indicator
  var range = '';
  var num = value;

  if (value.substring) {
    //We received an string
    //Remove lrtrim just in case...
    value = value.trim();

    var fields = value.split(' ');
    if (fields.length == 1) {
      //Only number received
      num = parseFloat(fields[0]);
    } else if (fields.length == 2) {
      range = fields[0];
      num = parseFloat(fields[1]);
    } else {
      return value;
    }
  }

  if (this.scientificNotation.indexOf(id) == -1) {
    var tmp = Math.round(num * 10) / 10;
    if (isNaN(tmp)) {
      return num;
    }
    return range + tmp.toFixed(1);
  } else {
    return range + num.toExponential(1);
  }
};

//add the signal measures of all virtual channels (profiles, plp...) of a channel.
MeasParser.prototype.addMeasuresInDataTable = function (meas_r, header_r, data_r) {
  function findSignalProperty(header_r, id) {
    for (var ii = 0; ii < header_r[0].length; ii++) {
      if (header_r[0][ii] == id) {
        return true;
      }
    }
    return false;
  }

  var it = meas_r.meas;
  while (it != null) {
    var next_it = null;
    for (var ii = 0; ii < it.length; ii++) {
      if (it[ii].hasOwnProperty('meas') == false) {
        data_r[it[ii].id] = this.format(it[ii].id, it[ii].value);
      } else {
        if (findSignalProperty(header_r, it[ii].id)) {
          if (data_r[it[ii].id] == it[ii].value) {
            next_it = it[ii].meas;
          }
        }
      }
    }
    it = next_it;
  }
};
