/**
 * metrics client data manager
 * By Joe Turgeon [http://arithmetric.com]
 * 2/15/2010
 */

var metricsData = function () {
  var dataCache = {}, dataSets = {}, dataSetNames = [], dataSetTitles = [], dataSetTags = [];

  /**
   * Data retrieval and processing routines
   */

  function getData(dataRequest) {
    processDataRequest(dataRequest);
  }

  function filterDataByIndex(setName, indexMin, indexMax) {
    var num = dataCache[setName].index.length;
    var dresults = {};
    var dindex = [];
    for (var i = 0; i < num; i++) {
      var idx = dataCache[setName].index[i];
      if (idx >= indexMin && idx <= indexMax) {
        dindex.push(idx);
        dresults[idx] = dataCache[setName].results[idx];
      }
    }
    return {results: dresults, index: dindex};
  }

  function processDataRequest(dataRequest) {
    var i, num, sn;

    // check if requested data is ready
    // request data that is not cached or already requested
    var ready = true;
    if (dataRequest.setNames && dataRequest.indexMin && dataRequest.indexMax) {
      num = dataRequest.setNames.length;
      for (i = 0; i < num; i++) {
        sn = dataRequest.setNames[i];
        if (!dataCache[sn] || !dataCache[sn].results || !dataCache[sn].stats || !dataCache[sn].stats.reqIndexMin || dataCache[sn].stats.reqIndexMin > dataRequest.indexMin || !dataCache[sn].stats.reqIndexMax || dataCache[sn].stats.reqIndexMax < dataRequest.indexMax) {
          ready = false;
          loadData(dataRequest, sn, dataRequest.indexMin, dataRequest.indexMax);
        }
      }
    }

    // if all data is ready, assemble data and send to target
    if (ready) {
      var data = {};
      var stats = [];
      for (i = 0; i < num; i++) {
        sn = dataRequest.setNames[i];
        data[sn] = filterDataByIndex(sn, dataRequest.indexMin, dataRequest.indexMax);
        data[sn].stats = buildDataStats(data[sn]);
        if (dataRequest.analysis && dataRequest.analysis !== 'NONE' && MetricsDataAnalysis.hasOwnProperty(dataRequest.analysis)) {
          data[sn] = performAnalysis(data[sn], dataRequest.analysis, dataRequest.analysisArgs);
        }
        data[sn].analysis = dataRequest.analysis;
        data[sn].analysisArgs = dataRequest.analysisArgs;
        data[sn].stats.reqIndexMin = dataRequest.indexMin;
        data[sn].stats.reqIndexMax = dataRequest.indexMax;
        stats.push(data[sn].stats);
      }
      data.stats = mergeDataStats(stats);
      dataRequest.target.handleData.call(dataRequest.target, data);
    }
  }

  function cacheData(setName, data) {
    if (data && data.results) {
      for (var key in data.results) {
        if (key && data.results.hasOwnProperty(key) && data.results[key].results && data.results[key].index && data.results[key].index.length) {
          dataCache[key] = {results: data.results[key].results, index: data.results[key].index, stats: buildDataStats(data.results[key], data.indexMin, data.indexMax)};
        }
      }
    }
  }

  function buildDataStats(dataObj, rimin, rimax) {
    var data = dataObj,
      dindex = dataObj.index,
      dresults = dataObj.results,
      dataNum = dindex.length,
      reqIndexMin = rimin ? Number(rimin) : 0,
      reqIndexMax = rimax ? Number(rimax) : 0,
      indexMin = 0,
      indexMax = 0,
      valueMin = 0,
      valueMax = 0,
      valueMinIndex = 0,
      valueMaxIndex = 0,
      i = 0,
      idx;

    for (i = 0; i < dataNum; i++) {
      idx = dindex[i];
      if (!i) {
        if (isFinite(idx)) {
          indexMin = indexMax = Number(idx);
        }
        else if (isFinite(dresults[idx].date)) {
          indexMin = indexMax = Number(dresults[idx].date);
        }
        if (isFinite(dresults[idx].value)) {
          valueMin = valueMax = Number(dresults[idx].value);
          valueMinIndex = valueMaxIndex = Number(idx);
        }
        if (isFinite(dresults[idx].high)) {
          valueMax = Number(dresults[idx].high);
          valueMaxIndex = Number(idx);
        }
        if (isFinite(dresults[idx].low)) {
          valueMin = Number(dresults[idx].low);
          valueMinIndex = Number(idx);
        }
      }
      else {
        if (isFinite(idx)) {
          if (indexMin > idx) {
            indexMin = Number(idx);
          }
          else if (indexMax < idx) {
            indexMax = Number(idx);
          }
        }
        else if (isFinite(dresults[idx].date)) {
          if (indexMin > dresults[idx].date) {
            indexMin = Number(dresults[idx].date);
          }
          else if (indexMax < dresults[idx].date) {
            indexMax = Number(dresults[idx].date);
          }
        }
        if (isFinite(dresults[idx].value)) {
          if (valueMin > dresults[idx].value) {
            valueMin = Number(dresults[idx].value);
            valueMinIndex = Number(idx);
          }
          else if (valueMax < dresults[idx].value) {
            valueMax = Number(dresults[idx].value);
            valueMaxIndex = Number(idx);
          }
        }
        if (isFinite(dresults[idx].high) && valueMax < dresults[idx].high) {
          valueMax = Number(dresults[idx].high);
          valueMaxIndex = Number(idx);
        }
        if (isFinite(dresults[idx].low) && valueMin > dresults[idx].low) {
          valueMin = Number(dresults[idx].low);
          valueMinIndex = Number(idx);
        }
      }
    }

    return {
      dataNum: dataNum,
      reqIndexMin: reqIndexMin,
      reqIndexMax: reqIndexMax,
      indexMin: indexMin,
      indexMax: indexMax,
      valueMin: valueMin,
      valueMax: valueMax,
      valueMinIndex: valueMinIndex,
      valueMaxIndex: valueMaxIndex
    };
  }

  function mergeDataStats(a) {
    var args = a;
    var num = args.length;
    var stats = {};
    for (var i = 0; i < num; i++) {
      var s = args[i];
      if (i === 0) {
        stats.dataNum = s.dataNum;
        stats.reqIndexMin = s.reqIndexMin;
        stats.reqIndexMax = s.reqIndexMax;
        stats.indexMin = s.indexMin;
        stats.indexMax = s.indexMax;
        stats.valueMin = s.valueMin;
        stats.valueMax = s.valueMax;
        stats.valueMinIndex = s.valueMinIndex;
        stats.valueMaxIndex = s.valueMaxIndex;
      }
      else {
        stats.dataNum += s.dataNum;
        if (stats.reqIndexMin > s.reqIndexMin) {
          stats.reqIndexMin = s.reqIndexMin;
        }
        if (stats.reqIndexMax > s.reqIndexMax) {
          stats.reqIndexMax = s.reqIndexMax;
        }
        if (stats.indexMin > s.indexMin) {
          stats.indexMin = s.indexMin;
        }
        if (stats.indexMax < s.indexMax) {
          stats.indexMax = s.indexMax;
        }
        if (stats.valueMin > s.valueMin) {
          stats.valueMin = s.valueMin;
          stats.valueMinIndex = s.valueMinIndex;
        }
        if (stats.valueMax < s.valueMax) {
          stats.valueMax = s.valueMax;
          stats.valueMaxIndex = s.valueMaxIndex;
        }
      }
    }
    return stats;
  }

  function performAnalysis(dataObj, analysis, args) {
    var res, newres, newidx, i, idx, num, lastval, winlen, sum;

    switch (analysis) {
      case 'NONE':
        break;

      case 'CHANGE':
        newres = {};
        newidx = [];
        lastval = 0;
        num = dataObj.index.length;
        for (i = 0; i < num; i++) {
          idx = dataObj.index[i];
          res = dataObj.results[idx];
          if (idx && res.hasOwnProperty('value')) {
            if (i) {
              newidx.push(idx);
              newres[idx] = {value: (res.value - lastval)};
            }
            lastval = res.value;
          }
        }
        dataObj.index = newidx;
        dataObj.results = newres;
        dataObj.stats = buildDataStats(dataObj);
        break;

      case 'CHANGE_PERCENT':
        newres = {};
        newidx = [];
        lastval = 0;
        num = dataObj.index.length;
        for (i = 0; i < num; i++) {
          idx = dataObj.index[i];
          res = dataObj.results[idx];
          if (idx && res.hasOwnProperty('value')) {
            if (i) {
              newidx.push(idx);
              newres[idx] = {value: (res.value - lastval) / lastval};
            }
            lastval = res.value;
          }
        }
        dataObj.index = newidx;
        dataObj.results = newres;
        dataObj.stats = buildDataStats(dataObj);
        break;

      case 'CUMULATIVE_CHANGE_PERCENT':
        newres = {};
        newidx = [];
        lastval = 0;
        num = dataObj.index.length;
        for (i = 0; i < num; i++) {
          idx = dataObj.index[i];
          res = dataObj.results[idx];
          if (idx && res.hasOwnProperty('value')) {
            if (i) {
              newres[idx] = {value: (res.value - lastval) / lastval};
            }
            else {
              lastval = res.value;
              newres[idx] = {value: 0};
            }
            newidx.push(idx);
          }
        }
        dataObj.index = newidx;
        dataObj.results = newres;
        dataObj.stats = buildDataStats(dataObj);
        break;

      case 'MOVING_AVERAGE':
        winlen = args && args.length && parseInt(args[0], 10) ? parseInt(args[0], 10) : 30;
        newres = {};
        newidx = [];
        lastval = [];
        num = dataObj.index.length;
        for (i = 0; i < num; i++) {
          idx = dataObj.index[i];
          res = dataObj.results[idx];
          if (idx && res.hasOwnProperty('value')) {
            lastval.push(res.value);
            if (lastval.length == winlen) {
              sum = 0;
              lastval.forEach(function (el) {sum += Number(el);});
              newidx.push(idx);
              newres[idx] = {value: sum / winlen};
              lastval.shift();
            }
          }
        }
        dataObj.index = newidx;
        dataObj.results = newres;
        dataObj.stats = buildDataStats(dataObj);
        break;
    }
    return dataObj;
  }

  function loadData(dataRequest, setName, indexMin, indexMax) {
    var dataobj = {
      req: 'load',
      setName: setName,
      indexMin: indexMin,
      indexMax: indexMax
    };

    $.ajax({
      data: dataobj,
      success: function (data, textStatus) {
        if (data.results) {
          cacheData(setName, data);
          processDataRequest(dataRequest);
        }
      },
      error: handleLoadError,
      url: 'json.php',
//      type: 'post',
      cache: false,
      dataType: 'json'
    });
  }

  function handleLoadError(XMLHttpRequest, textStatus, errorThrown) {
    alert('An error occurred while trying to fetch data. Please try again.');
  }

  /**
   * Data Set utility routines
   */

  function validDataSet(name) {
    // scan data set names for matches
    name = name.toLowerCase();
    var names = dataSetNames.length;
    for (var i = 0; i < names; i++) {
      if (String(dataSetNames[i]).toLowerCase() == name) {
        return true;
      }
    }
    return false;
  }

  function getDataSet(name) {
//TODO: this does not work for fields that are named
    return dataSets[name];
  }

  function getDataSets() {
    return dataSets;
  }

  function getDataSetNames() {
    return dataSetNames;
  }

  function getDataSetTitle(name) {
    if (dataSets[name]) {
      if (dataSets[name].title && dataSets[name].title.length) {
        return dataSets[name].title;
      }
      else if (dataSets[name].parentDataSet && dataSets[dataSets[name].parentDataSet].title && dataSets[dataSets[name].parentDataSet].title.length) {
        return dataSets[dataSets[name].parentDataSet].title;
      }
    }
    return name;
  }

  function getDataSetTitles() {
    return dataSetTitles;
  }

  function getDataSetTags() {
    return dataSetTags;
  }

  function getDataSetChildren(name) {
    var results = [];
    var num = dataSetNames.length;
    for (var i = 0; i < num; i++) {
      var sn = dataSetNames[i];
      if (dataSets[sn] && dataSets[sn].parentDataSet && dataSets[sn].parentDataSet === name) {
        results.push(sn);
      }
    }
    return results;
  }

  function findDataSets(queryObject) {
    var matches = [], i, l;

    // prepare query words
    var queryWords = queryObject.words;
    var queryWordNum = queryWords.length;
    for (l = 0; l < queryWordNum; l++) {
      queryWords[l] = String(queryWords[l]).toLowerCase();
    }

    // scan data set name and title for matches
    var names = dataSetNames.length;
    nextname:
    for (i = 0; i < names; i++) {
      // test if each query word appears in the name or title
      for (l = 0; l < queryWordNum; l++) {
        if (String(dataSetNames[i]).toLowerCase().indexOf(queryWords[l]) == -1 && String(dataSetTitles[i]).toLowerCase().indexOf(queryWords[l]) == -1) {
          continue nextname;
        }
      }
      // filter matches by tags
//TODO: this does not work for fields that are named
      if (queryObject.tag && (!dataSets[dataSetNames[i]] || !dataSets[dataSetNames[i]].tags || dataSets[dataSetNames[i]].tags !== queryObject.tag)) {
        continue;
      }
      matches.push(i);
    }

    // sort by array index, eliminate duplicates, return names and title
    var matchdata = [];
    if (matches.length) {
      matches.sort(function (a, b) {return a - b;});
      var len = matches.length;
      for (i = 0; i < len; i++) {
        if (!i || matches[i - 1] != matches[i]) {
          matchdata.push({name: dataSetNames[matches[i]], title: dataSetTitles[matches[i]]});
        }
      }
    }
    return matchdata;
  }

  function loadDataSets() {
    var dataobj = {req: 'loadsets'};

    $.ajax({
      data: dataobj,
      success: handleLoadDataSets,
      error: handleLoadDataSetsError,
      url: 'json.php',
//      type: 'post',
      cache: false,
      dataType: 'json'
    });
  }

  function handleLoadDataSets(data, textStatus) {
    var res = data.results;
    var len = res.length;
    var sets = {};
    var names = [];
    var titles = [];
    var tags = [];
    for (var i = 0; i < len; i++) {
      if (String(res[i].name).length) {
        sets[res[i].name] = res[i];
        names.push(res[i].name);
        titles.push(String(res[i].title).length ? res[i].title : '');
        if (res[i].tags && tags.indexOf(res[i].tags) === -1) {
          tags.push(String(res[i].tags));
        }
      }
      if (res[i].fields && res[i].fields.length) {
        var fields = res[i].fields.length;
        for (var j = 0; j < fields; j++) {
          if (String(res[i].fields[j].name).length && res[i].fields[j].type != 'index') {
            var fdata = res[i].fields[j];
            fdata.parentDataSet = res[i].name;
            sets[res[i].fields[j].name] = fdata;
            names.push(res[i].fields[j].name);
            titles.push(String(res[i].fields[j].title).length ? res[i].fields[j].title : '');
            if (res[i].fields[j].tags && tags.indexOf(res[i].fields[j].tags) === -1) {
              tags.push(String(res[i].fields[j].tags));
            }
          }
        }
      }
    }
    dataSets = sets;
    dataSetNames = names;
    dataSetTitles = titles;
    dataSetTags = tags;
  }

  function handleLoadDataSetsError(XMLHttpRequest, textStatus, errorThrown) {
    alert('An error occurred while trying to fetch the data set index. Please reload the page and try again.');
  }

  // Define public methods and properties
  return {
    getData: getData,
    buildDataStats: buildDataStats,
    mergeDataStats: mergeDataStats,
    validDataSet: validDataSet,
    getDataSet: getDataSet,
    getDataSets: getDataSets,
    getDataSetNames: getDataSetNames,
    getDataSetTitle: getDataSetTitle,
    getDataSetTitles: getDataSetTitles,
    getDataSetTags: getDataSetTags,
    getDataSetChildren: getDataSetChildren,
    findDataSets: findDataSets,
    loadDataSets: loadDataSets
  };
}();

var MetricsDataRequest = function (target, setNames, indexMin, indexMax, analysis, analysisArgs) {
  var self = (this === window) ? {} : this;
  self.target = target || false;
  self.setNames = setNames || false;
  self.indexMin = indexMin || false;
  self.indexMax = indexMax || false;
  self.analysis = analysis || false;
  self.analysisArgs = analysisArgs || false;
  return self;
};

var MetricsDataAnalysis = {
  'NONE': 'None',
  'CHANGE': 'Change',
  'CHANGE_PERCENT': 'Percent change',
  'CUMULATIVE_CHANGE_PERCENT': 'Cumulative percent change',
  'MOVING_AVERAGE': 'Moving average'
};
