"use strict";

const _ = require('../../../utils/under-dash');
const utils = require('../../../utils/utils');
const colCache = require('../../../utils/col-cache');
const BaseXform = require('../base-xform');
const Range = require('../../../doc/range');
function assign(definedName, attributes, name, defaultValue) {
  const value = attributes[name];
  if (value !== undefined) {
    definedName[name] = value;
  } else if (defaultValue !== undefined) {
    definedName[name] = defaultValue;
  }
}
function assignBool(definedName, attributes, name, defaultValue) {
  const value = attributes[name];
  if (value !== undefined) {
    definedName[name] = utils.parseBoolean(value);
  } else if (defaultValue !== undefined) {
    definedName[name] = defaultValue;
  }
}
function optimiseDataValidations(model) {
  // Squeeze alike data validations together into rectangular ranges
  // to reduce file size and speed up Excel load time
  const dvList = _.map(model, (dataValidation, address) => ({
    address,
    dataValidation,
    marked: false
  })).sort((a, b) => _.strcmp(a.address, b.address));
  const dvMap = _.keyBy(dvList, 'address');
  const matchCol = (addr, height, col) => {
    for (let i = 0; i < height; i++) {
      const otherAddress = colCache.encodeAddress(addr.row + i, col);
      if (!model[otherAddress] || !_.isEqual(model[addr.address], model[otherAddress])) {
        return false;
      }
    }
    return true;
  };
  return dvList.map(dv => {
    if (!dv.marked) {
      const addr = colCache.decodeEx(dv.address);
      if (addr.dimensions) {
        dvMap[addr.dimensions].marked = true;
        return {
          ...dv.dataValidation,
          sqref: dv.address
        };
      }

      // iterate downwards - finding matching cells
      let height = 1;
      let otherAddress = colCache.encodeAddress(addr.row + height, addr.col);
      while (model[otherAddress] && _.isEqual(dv.dataValidation, model[otherAddress])) {
        height++;
        otherAddress = colCache.encodeAddress(addr.row + height, addr.col);
      }

      // iterate rightwards...

      let width = 1;
      while (matchCol(addr, height, addr.col + width)) {
        width++;
      }

      // mark all included addresses
      for (let i = 0; i < height; i++) {
        for (let j = 0; j < width; j++) {
          otherAddress = colCache.encodeAddress(addr.row + i, addr.col + j);
          dvMap[otherAddress].marked = true;
        }
      }
      if (height > 1 || width > 1) {
        const bottom = addr.row + (height - 1);
        const right = addr.col + (width - 1);
        return {
          ...dv.dataValidation,
          sqref: `${dv.address}:${colCache.encodeAddress(bottom, right)}`
        };
      }
      return {
        ...dv.dataValidation,
        sqref: dv.address
      };
    }
    return null;
  }).filter(Boolean);
}
class DataValidationsXform extends BaseXform {
  get tag() {
    return 'dataValidations';
  }
  render(xmlStream, model) {
    const optimizedModel = optimiseDataValidations(model);
    if (optimizedModel.length) {
      xmlStream.openNode('dataValidations', {
        count: optimizedModel.length
      });
      optimizedModel.forEach(value => {
        xmlStream.openNode('dataValidation');
        if (value.type !== 'any') {
          xmlStream.addAttribute('type', value.type);
          if (value.operator && value.type !== 'list' && value.operator !== 'between') {
            xmlStream.addAttribute('operator', value.operator);
          }
          if (value.allowBlank) {
            xmlStream.addAttribute('allowBlank', '1');
          }
        }
        if (value.showInputMessage) {
          xmlStream.addAttribute('showInputMessage', '1');
        }
        if (value.promptTitle) {
          xmlStream.addAttribute('promptTitle', value.promptTitle);
        }
        if (value.prompt) {
          xmlStream.addAttribute('prompt', value.prompt);
        }
        if (value.showErrorMessage) {
          xmlStream.addAttribute('showErrorMessage', '1');
        }
        if (value.errorStyle) {
          xmlStream.addAttribute('errorStyle', value.errorStyle);
        }
        if (value.errorTitle) {
          xmlStream.addAttribute('errorTitle', value.errorTitle);
        }
        if (value.error) {
          xmlStream.addAttribute('error', value.error);
        }
        xmlStream.addAttribute('sqref', value.sqref);
        (value.formulae || []).forEach((formula, index) => {
          xmlStream.openNode(`formula${index + 1}`);
          if (value.type === 'date') {
            xmlStream.writeText(utils.dateToExcel(new Date(formula)));
          } else {
            xmlStream.writeText(formula);
          }
          xmlStream.closeNode();
        });
        xmlStream.closeNode();
      });
      xmlStream.closeNode();
    }
  }
  parseOpen(node) {
    switch (node.name) {
      case 'dataValidations':
        this.model = {};
        return true;
      case 'dataValidation':
        {
          this._address = node.attributes.sqref;
          const dataValidation = {
            type: node.attributes.type || 'any',
            formulae: []
          };
          if (node.attributes.type) {
            assignBool(dataValidation, node.attributes, 'allowBlank');
          }
          assignBool(dataValidation, node.attributes, 'showInputMessage');
          assignBool(dataValidation, node.attributes, 'showErrorMessage');
          switch (dataValidation.type) {
            case 'any':
            case 'list':
            case 'custom':
              break;
            default:
              assign(dataValidation, node.attributes, 'operator', 'between');
              break;
          }
          assign(dataValidation, node.attributes, 'promptTitle');
          assign(dataValidation, node.attributes, 'prompt');
          assign(dataValidation, node.attributes, 'errorStyle');
          assign(dataValidation, node.attributes, 'errorTitle');
          assign(dataValidation, node.attributes, 'error');
          this._dataValidation = dataValidation;
          return true;
        }
      case 'formula1':
      case 'formula2':
        this._formula = [];
        return true;
      default:
        return false;
    }
  }
  parseText(text) {
    if (this._formula) {
      this._formula.push(text);
    }
  }
  parseClose(name) {
    switch (name) {
      case 'dataValidations':
        return false;
      case 'dataValidation':
        {
          if (!this._dataValidation.formulae || !this._dataValidation.formulae.length) {
            delete this._dataValidation.formulae;
            delete this._dataValidation.operator;
          }
          // The four known cases: 1. E4:L9 N4:U9  2.E4 L9  3. N4:U9  4. E4
          const list = this._address.split(/\s+/g) || [];
          list.forEach(addr => {
            if (addr.includes(':')) {
              const range = new Range(addr);
              range.forEachAddress(address => {
                this.model[address] = this._dataValidation;
              });
            } else {
              this.model[addr] = this._dataValidation;
            }
          });
          return true;
        }
      case 'formula1':
      case 'formula2':
        {
          let formula = this._formula.join('');
          switch (this._dataValidation.type) {
            case 'whole':
            case 'textLength':
              formula = parseInt(formula, 10);
              break;
            case 'decimal':
              formula = parseFloat(formula);
              break;
            case 'date':
              formula = utils.excelToDate(parseFloat(formula));
              break;
            default:
              break;
          }
          this._dataValidation.formulae.push(formula);
          this._formula = undefined;
          return true;
        }
      default:
        return true;
    }
  }
}
module.exports = DataValidationsXform;
//# sourceMappingURL=data-validations-xform.js.map
