//@include lib_Class.js

/**
 * Rendering Excel file from a Excel template.
 *
 * This can either be used for creating a new Document in the archive (`targetId` specified) or can create a new version on an existing object (`objId`).
 *
 * Which fields will be written, is specified by the template (first line of the data worksheet contains the ObjKeys)
 * or by the first Sord of the data.sords array (all ObjKeys).
 *
 * @author ELO Digital Office GmbH
 * @version 1.1
 *
 * @eloas
 *
 * @requires sol.common.StringUtils
 * @requires sol.common.ObjectUtils
 * @requires sol.common.DateUtils
 * @requires sol.common.SordUtils
 * @requires sol.common.RepoUtils
 * @requires sol.common.Template
 * @requires sol.common.as.ExcelDocument
 *
 */
sol.define("sol.common.as.renderer.Excel", {

  requiredConfig: ["templateId"],

  /**
   * @cfg {String} templateId (required)
   * The template which should be rendered
   */

  /**
   * @cfg {String} targetId
   * The target folder to store the rendered document
   */

  /**
   * @cfg {String} objId
   * The rendered document will be saved as a version of this object
   * Either `targetId` or `objId` has to be defined.
   */

  /**
   * @cfg {Boolean} copyMetaData
   * If `true`, the mask and the metadata of the template will be copied to the new document
   */

  /**
   * @cfg {String} [datasheet="Data"]
   * Specifies on which worksheet the data should be written
   */

  /**
   * @private
   * @property {String} mapPrefix
   */
  mapPrefix: "MAP_",

  initialize: function (config) {
    var me = this;
    me.$super("sol.Base", "initialize", [config]);
    if (!me.objId && !me.targetId) {
      throw "IllegalArgumentException: either 'targetId' or 'objId' has to defined";
    }
    me.datasheet = config.datasheet || "Data";
    me._mapKeys = [];
  },

  /**
   * Starts the template rendering.
   * @param {String} name Name
   * @param {String} data Data
   * @return {Object}
   */
  render: function (name, data) {
    var me = this,
        config = {},
        templateSord;

    if (me.objId) {
      config.objId = me.objId;
    } else {
      config.name = name;
      config.parentId = me.targetId;
    }

    config.format = "xlsx";
    if (me.copyMetaData === true) {
      templateSord = ixConnect.ix().checkoutSord(me.templateId, SordC.mbAllIndex, LockC.NO);
      config.maskId = templateSord.mask;
      config.objKeysObj = sol.common.SordUtils.getTemplateSord(templateSord).sord.objKeys;
    }

    return me.renderExcel(data, config);
  },

  /**
   * @private
   * @param {Object} data Data
   * @param {Object} config Configuration
   * @return {Object}
   */
  renderExcel: function (data, config) {
    var me = this,
        result = {},
        excelDocument;

    try {
      excelDocument = sol.create("sol.common.as.ExcelDocument", {});
      excelDocument.openFromRepo({ objId: me.templateId });
      me.fillExcelSpreadsheet(excelDocument, data);
      result.objId = excelDocument.saveToRepo(config);
    } catch (ex) {
      me.logger.error(["error rendering report '{0}' (templateId='{1}')", config.name, me.templateId], ex);
    }

    return result;
  },

  /**
   * @private
   * Fills the data to an Excel spreadsheet
   * @param {sol.common.as.ExcelDocument} excelDocument Excel document
   * @param {Object} data Data   *
   */
  fillExcelSpreadsheet: function (excelDocument, data) {
    var me = this,
        workbook = excelDocument.workbook;

    me._worksheet = workbook.worksheets.get(me.datasheet);

    if (!me._worksheet) {
      return;
    }

    // Excel renderer would be used in combination with ParentDataCollector.
    // ParentDataCollector will always return data.sord as one object.
    // So we ensure that data.sords is an array with 1 element
    if (data && data.sord && !data.sords) {
      data.sords = [data.sord];
    }

    if (data && data.sords && (data.sords.length > 0)) {
      me.prepareColumnMapping(data.sords[0]);
      me.prepareHeader();
      me.writeData(data.sords);
    }
  },

  /**
   * @private
   * @param {de.elo.ix.client.Sord} sord Sord
   */
  prepareColumnMapping: function (sord) {
    var me = this,
        header = me.readHeader(sord),
        columnIdx, columnDescObj;

    me._columnMapping = {};
    me._header = [];
    if (header) {
      for (columnIdx = 0; columnIdx < header.length; columnIdx++) {
        columnDescObj = header[columnIdx];
        columnDescObj.idx = columnIdx;
        me._columnMapping[columnDescObj.key] = columnDescObj;
        me._header.push(columnDescObj.key); // to preserve header order
      }
    }
  },

  /**
   * @private
   * @param {de.elo.ix.client.Sord} sord Sord
   * @return {Array}
   */
  readHeader: function (sord) {
    var me = this,
        header = me.readHeaderFromWorksheet();
    if (!header) {
      header = me.readHeaderFromSord(sord);
    }
    return header;
  },

  /**
   * @private
   * @return {Array}
   */
  readHeaderFromWorksheet: function () {
    var me = this,
        cells = me._worksheet.getCells(),
        columnIdx = 0,
        cell = cells.getCell(0, columnIdx++),
        headerData = [],
        value;

    while (cell && cell.getValue() !== null) {
      value = {
        key: cell.getValue() + "",
        isDate: me.isDate(cell),
        isNumber: me.isNumber(cell)
      };

      headerData.push(value);

      if (sol.common.StringUtils.startsWith(value.key, me.mapPrefix)) {
        me._mapKeys.push(value.key.replace(me.mapPrefix, "") + "*");
      }

      cell = cells.getCell(0, columnIdx++);
    }

    return (headerData.length > 0) ? headerData : null;
  },

  /**
   * @private
   * Checks, if a Cell is formated as number.
   * @param {com.aspose.cells.Cell} cell
   * @return {Boolean}
   */
  isNumber: function (cell) {
    var styleNumber = cell.style.getNumber();
    return (styleNumber > 0 && styleNumber < 14) || (styleNumber > 22 && styleNumber < 45);
  },

  /**
   * @private
   * Checks, if a Cell is formated as date.
   * @param {com.aspose.cells.Cell} cell
   * @return {Boolean}
   */
  isDate: function (cell) {
    return cell.style.isDateTime();
  },

  /**
   * @private
   * @param {de.elo.ix.client.Sord} sord Sord
   * @return {Array}
   */
  readHeaderFromSord: function (sord) {
    var headerData = [],
        objKey;
    for (objKey in sord.objKeys) {
      if (sord.objKeys.hasOwnProperty(objKey)) {
        headerData.push({ key: objKey });
      }
    }
    return (headerData.length > 0) ? headerData : null;
  },

  /**
   * @private
   */
  prepareHeader: function () {
    var me = this;

    me.writeLine(0, me._header);
  },

  /**
   * @private
   * @param {de.elo.ix.client.Sord[]} rowSords Row Sords
   */
  writeData: function (rowSords) {
    var me = this,
        line = 1;
    rowSords.forEach(function (rowSord) {
      var rows;
      if (me._mapKeys.length > 0) {
        rows = me.expandRowSord(rowSord);
        if (rows) {
          rows.forEach(function (row) {
            me.writeLine(line++, row);
          });
        }
      } else {
        me.writeLine(line++, rowSord.objKeys);
      }
    });
  },

  /**
   * @private
   * Extract the lines for the map items.
   * If there is a map value with an index, the line (the ObjKeys) will be repeated for each map index.
   * If the `rowSord` contains `mapKeys`, those will be used and the map values will not be checked out again.
   * @param {de.elo.ix.client.Sord} rowSord Row sord
   * @return {Object}
   */
  expandRowSord: function (rowSord) {
    var me = this,
        result = [],
        mapitems, mapkey, tmpRow;

    if (rowSord.mapKeys && !sol.common.ObjectUtils.isEmpty(rowSord.mapKeys)) {
      mapitems = [];
      for (mapkey in rowSord.mapKeys) {
        if (rowSord.mapKeys.hasOwnProperty(mapkey)) {
          mapitems.push(new KeyValue(mapkey, rowSord.mapKeys[mapkey]));
        }
      }
    } else {
      mapitems = ixConnect.ix().checkoutMap(MapDomainC.DOMAIN_SORD, rowSord.id, me._mapKeys, LockC.NO).items;
    }

    mapitems.forEach(function (mapitem) {
      var key = me.mapPrefix + mapitem.key,
          idx = me.getIndexFromName(key);
      if (idx) {
        if (!result[idx - 1]) {
          result[idx - 1] = me.copyObject(rowSord);
        }
        result[idx - 1][sol.common.StringUtils.removeTrailingNumber(key)] = mapitem.value + "";
      } else {
        if (!tmpRow) {
          tmpRow = me.copyObject(rowSord);
        }
        tmpRow[key] = mapitem.value + "";
      }
    });

    if (tmpRow) {
      result.push(tmpRow);
    }

    return (result.length > 0) ? result : [rowSord.objKeys];
  },


  /**
   * @private
   * @param {de.elo.ix.client.Sord} rowSord Row sord
   * @return {Object}
   */
  copyObject: function (rowSord) {
    var copy = {},
        prop;
    for (prop in rowSord.objKeys) {
      if (rowSord.objKeys.hasOwnProperty(prop)) {
        copy[prop] = rowSord.objKeys[prop];
      }
    }
    return copy;
  },

  /**
   * @private
   * @param {String} fieldName
   * @return {String}
   */
  getIndexFromName: function (fieldName) {
    if (!fieldName) {
      return null;
    }
    return sol.common.StringUtils.getTrailingNumber(fieldName);
  },

  /**
   * @private
   * @param {Number} rowIndex Row index
   * @param {Array} rowData Row data
   */
  writeLine: function (rowIndex, rowData) {
    var me = this,
        cells = me._worksheet.getCells(),
        columnIdx, column, value;

    if (sol.common.ObjectUtils.isArray(rowData)) {
      for (columnIdx = 0; columnIdx < rowData.length; columnIdx++) {
        cells.getCell(rowIndex, columnIdx).putValue(rowData[columnIdx]);
      }
    } else {
      for (column in rowData) {
        if (rowData.hasOwnProperty(column) && rowData[column] && me._columnMapping.hasOwnProperty(column)) {
          if (me._columnMapping[column].isDate === true) {
            value = new java.util.Date(sol.common.DateUtils.isoToDate(rowData[column]).getTime());
          } else if (me._columnMapping[column].isNumber === true) {
            try {
              value = java.lang.Double.parseDouble(rowData[column].replace(",", "."));
            } catch (ex) {
              value = rowData[column];
            }
          } else {
            value = rowData[column];
          }
          cells.getCell(rowIndex, me._columnMapping[column].idx).putValue(value);
        }
      }
    }
  }
});