/**
 * Server-side Report builder
 *
 * @example

// Server side report generation
const fs = require('fs');
const UBReport = require('@unitybase/ubs/modules/UBServerReport');
let reportResult = UBReport.makeReport('test','pdf',{});
if (reportResult.reportType === 'pdf') {
 console.debug('Generate a PDF report of size=', result.reportData.byteLength)
 fs.writeFileSync('d:/result.pdf', result.reportData )
} else {
 console.debug('Generate a HTML report of size=', result.reportData.length)
 fs.writeFileSync('d:/result.html', result.reportData )
}

 * @module UBServerReport
 * @memberOf module:@unitybase/ubs
 */
module.exports = {
  /**
   * Render report with code `reportCode` on the server side
   * Report templates and creation rules are defined in `ubs_report` entity.
   *
   * @param {string|Object} reportCode If reportCode type is Object it is a config object { code: String, type: String, params: String|Object }
   * @param {string} [reportType='html'] Possible values: 'html'|'pdf'
   * @param {*} params Any parameters passed to the buildReport function from report code block
   * @returns {ReportResult}
   */
  makeReport: function (reportCode, reportType, params) {
    const report = new UBServerReport(reportCode, reportType, params)
    return report.makeReport()
  }
}

const Mustache = require('mustache')
// TODO - remove Q.when in future - it added for compatibility with .then() on client
// eslint-disable-next-line no-unused-vars
const Q = require('when')
const _ = require('lodash')
const path = require('path')
const UB = require('@unitybase/ub')
const Session = UB.Session
const App = UB.App
const formatFunctions = require('../public/formatFunctions')

const xmldom = require('xmldom')
global.DOMParser = xmldom.DOMParser
global.XMLSerializer = xmldom.XMLSerializer

// PDF unicode-text require atob & btoa to be in global
global.atob = function (text) {
  const buf = Buffer.from(text, 'base64')
  return buf.toString('binary') // buffer.toString('utf-8')
}

/**
 * Report generation result
 * @typedef {Object} ReportResult
 * @property {string} reportCode
 * @property {string} reportType
 * @property {Object} incomeParams
 * @property {Object} reportOptions
 * @property {string|ArrayBuffer|*} reportData Result of buildReport function execution
 */

/**
 * Server-side report builder
 * @class
 * @protected
 * @param {string|Object} reportCode
 * If reportCode type is Object it is a config object { code: String, type: String, params: String|Object }
 * @param {string} [reportType='html'] Possible values: 'html'|'pdf'
 * @param {{}} params
 * @param {String} [language=Session.userLang]
 */
function UBServerReport (reportCode, reportType, params, language) {
  /**
   * Report code
   * @property {string} reportCode
   */
  this.reportCode = reportCode
  /**
   * Possible values: 'html', 'pdf'
   * @property {string} reportCode
   */
  this.reportType = reportType || 'html'
  /**
   * Report parameters
   * @property {{}} incomeParams
   */
  this.incomeParams = params || {}
  /**
   * The options of report. Known options: pageOrientation.
   * @property {{}} reportOptions
   */
  this.reportOptions = {}

  this.lang = language || Session.userLang

  if (typeof reportCode === 'object') {
    this.reportCode = reportCode.code
    this.reportType = reportCode.type || 'html'
    this.incomeParams = reportCode.params
    this.lang = reportCode.language || Session.userLang
    this.debug = reportCode.debug
  }
}

/**
* Load report template and code.
*/
UBServerReport.prototype.init = function () {
  const reportInfo = UB.Repository('ubs_report')
    .attrs(['ID', 'report_code', 'name', 'template', 'code', 'model'])
    .where('[report_code]', '=', this.reportCode)
    .selectSingle()

  if (!reportInfo) throw new Error(`Report with code "${this.reportCode}" not found`)

  this.reportRW = {
    ID: reportInfo.ID,
    name: reportInfo.name,
    template: reportInfo.template,
    code: reportInfo.code,
    model: reportInfo.model,
    report_code: this.reportCode
  }
  this.reportRW.templateData = this.getDocument('template')
  this.prepareTemplate()
  // loaded in prepareCode this.reportRW.codeData =  this.getDocument('code');
  this.prepareCode()
}

UBServerReport.prototype.prepareTemplate = function () {
  const reOptions = /(<!--%\w+?:(.+?)-->)/gi
  // <!--{{userrole}}-->
  this.reportRW.templateData = this.reportRW.templateData.replace(/<!--({{.+?}})-->/g, '$1')
  // <!--@name "123"-->
  this.reportRW.templateData = this.reportRW.templateData.replace(/<!--@\w+?[ ]*?".+?"-->/g, '')

  // parse options
  const matches = this.reportRW.templateData.match(reOptions)
  if (matches && matches.length > 0) {
    _.forEach(matches, (item) => {
      let itemVal = item.match(/<!--%(\w+?:.+?)-->/)[1]
      itemVal = itemVal.split(':')
      this.reportOptions[itemVal[0]] = itemVal[1]
    })
    // value = value.replace(reOptions, '');
  }
}

/**
 * Render report on server
 * @example

const UBReport = require('@unitybase/ubs/modules/UBServerReport')
let reportResult = UBReport.makeReport('reportCode', 'html', {
  login: email,
  password: password,
  activateUrl: registrationAddress,
  appConfig: App.serverConfig
})

 * @param {Object} [params]
 * @returns {ReportResult}
 */
UBServerReport.prototype.makeReport = function (params) {
  let prm = _.clone(this.incomeParams)
  if (params) {
    prm = _.assign(prm, params)
  }
  this.init()
  const promiseOrData = this.buildReport(prm)
  let report
  let errInsidePromise
  // convert to value
  if (promiseOrData && (typeof promiseOrData.then === 'function')) {
    promiseOrData.then(
      data => { report = data },
      err => { errInsidePromise = err }
    )
  } else {
    report = promiseOrData
  }
  if (errInsidePromise) throw errInsidePromise // rethrow real error
  return this.makeResult(report)
}

/**
* Render HTML report
* @param {Object} reportData
* @returns {string}
*/
UBServerReport.prototype.buildHTML = function (reportData) {
  if (!reportData || (typeof reportData !== 'object') || reportData instanceof Array) {
    throw new Error('reportData must be a Object')
  }
  formatFunctions.addBaseMustacheSysFunction(reportData, this.lang)
  formatFunctions.addMustacheSysFunction(reportData, this.lang)
  return Mustache.render(this.reportRW.templateData, reportData)
}

/**
* Prepare PDF report from html
* @param {string} html
* @param {Object} options
* @param {Array|Object} [options.fonts]
* [{ fontName: "TimesNewRoman", fontStyle: "Normal" }, ..]
* @param {Boolean} [options.outputPdf] If it is not False build PDF output at end. By default it is True.
* @returns {Promise} A promise that resolves with an ArrayBuffer with PDF or instance of PDF.csPrintToPdf with rendered HTML on it when options.outputPdf is false.
*/
UBServerReport.prototype.transformToPdf = function (html, options = {}) {
  const PDF = require('@unitybase/pdf')

  PDF.PrintToPdf.requireFonts({
    fonts: options.fonts
      ? options.fonts
      : [
          { fontName: 'TimesNewRoman', fontStyle: 'Normal' },
          { fontName: 'TimesNewRoman', fontStyle: 'Bold' },
          { fontName: 'TimesNewRoman', fontStyle: 'Italic' },
          { fontName: 'TimesNewRoman', fontStyle: 'BoldItalic' }
        ]
  })

  let pdfConfig = {
    orientation: this.reportOptions.pageOrientation === 'landscape' ? 'l' : 'p',
    font: {
      name: 'TimesNewRoman',
      type: 'Normal',
      size: 12
    },
    margin: {
      top: 5,
      right: 5,
      bottom: 8,
      left: 5
    }
  }
  if (this.onTransformConfig) {
    pdfConfig = this.onTransformConfig(pdfConfig)
  }
  const pdf = new PDF.PrintToPdf(pdfConfig)
  pdf.writeHtml({
    html: html,
    orientation: this.reportOptions.pageOrientation,
    onPositionDetermine: options.onPositionDetermine
  })
  this.pdf = pdf
  if (options.outputPdf === false) {
    return pdf
  } else {
    return pdf.output('arrayBuffer').buffer
  }
}

/**
* @private
* @param {ArrayBuffer|String} reportData
* @returns {Object}
*/
UBServerReport.prototype.makeResult = function (reportData) {
  if (!reportData) {
    throw new Error(`Report ${this.reportCode} return empty report`)
  }
  return {
    reportCode: this.reportCode,
    reportType: this.reportType,
    incomeParams: this.incomeParams,
    reportOptions: this.reportOptions,
    reportData: reportData
  }
}

/**
* Apply user code to itself
* @private
*/
UBServerReport.prototype.prepareCode = function () {
  const modelPublicPath = App.domainInfo.models[this.reportRW.model].realPublicPath
  const reportModulePath = path.join(modelPublicPath, 'reports', this.reportRW.report_code + '.js')
  const reportModule = require(reportModulePath)
  if (reportModule.reportCode) {
    _.forEach(reportModule.reportCode, (val, name) => {
      this[name] = val
    })
  }
}

/**
 * This function must be defined in report code block.
 *
 * Inside function you must:
 *
 *  - Prepare data
 *  - Run method this.buildHTML(reportData); where reportData is data for mustache template
 *  - If need create PDF run method this.buildPdf(htmlReport); where htmlReport is HTML
 *  - If is server side function must return report as string otherwise Promise
 *
 * @cfg {function} buildReport
 * @param {Object} reportParams
 * @returns {Promise|Object|String} If code run at server, method must return report data, else - Promise, which resolves to report data
 */
UBServerReport.prototype.buildReport = function (reportParams) {
  throw new UB.UBError('Function "buildReport" not defined in report code block')
}

/**
* This function used by ReportViewer.
* If function exists and return UBS.ReportParamForm or Array ReportViewer show this panel on the top of viewer form.
* Example
*
*      onParamPanelConfig: function() {
*           var paramForm = Ext.create('UBS.ReportParamForm', {
*           items: [{
*                  xtype: 'textfield',
*                  name: 'name',
*                  fieldLabel: 'Name'
*              }, {
*                  xtype: 'datefield',
*                  name: 'birthday',
*                  fieldLabel: 'Birthday'
*              }, ],
*              getParameters: function(owner) {
*                  var frm = owner.getForm();
*                  return {
*                      name: frm.findField('name').getValue(),
*                      birthday: frm.findField('birthday').getValue()
*                  };
*              }
*          });
*          return paramForm;
*      }
*
* or
*
*      onParamPanelConfig: function() {
*           return [{
*                  xtype: 'textfield',
*                  name: 'name',
*                  fieldLabel: 'Name'
*              }, {
*                  xtype: 'datefield',
*                  name: 'birthday',
*                  fieldLabel: 'Birthday'
*              } ];
*      }
*
*
* @cfg {function} onParamPanelConfig
*/

/**
* You can edit config for pdf in this function
*
* @cfg {function} onTransformConfig
* @param {Object} config
* @returns {Object}
*/

/**
* load document
* @param {string} attribute
* @returns {string}
*/
UBServerReport.prototype.getDocument = function (attribute) {
  const cfg = JSON.parse(this.reportRW[attribute])

  return App.blobStores.getContent({
    entity: 'ubs_report',
    attribute: attribute,
    ID: this.reportRW.ID,
    isDirty: !!cfg.isDirty
  }, { encoding: 'utf8' })
}