var debug = require('debug')('ErrorHandler');
var Errors = require('../data/errors.json');
var hintSpec = require('../data/hint_spec.json');
var CssPropertySpec = require('../data/css_property_datatype.json');
var CssPrimitiveSpec = require('../data/css_primitive_datatype.json');
var HtmlSpec = require('../data/html_spec.json');

var errorLevel = {
    'PASS' : 0,
    'WARNING' : 1,
    'ERROR' : 2
};

var ErrorHandler = function() {
  if (!(this instanceof ErrorHandler)) {
    return new ErrorHandler();
  }

  this.errors = [];
  this.profileConfig = null;
}

ErrorHandler.prototype.printError = function(code, filePath, line, col, args, ref) {
  if (!Errors.hasOwnProperty(code))
    throw new TypeError('unknown error name.');

  var error = Errors[code];
  if (error.level === 'PASS') return;

  var levelMsg = (error.level === 'ERROR') ? 'E' : 'W';
  var fmtMsg = error.msg;

  if (Array.isArray(args)) {
    for (var i in args) {
      var exp = new RegExp('%'+i, "g");
      fmtMsg = fmtMsg.replace(exp, args[i]);
    }
  }

  var msg = { code: code
            , args: args
            , filePath: filePath
            , line: line
            , col: col
            , levelMsg: levelMsg
            , fmtMsg: fmtMsg
            , hint: ''};

  // Remove the dulplicated error message.
  for (var i in this.errors) {
    if ( this.errors[i].filePath == msg.filePath
      && this.errors[i].line == msg.line
      && this.errors[i].col == msg.col
      && this.errors[i].levelMsg == msg.levelMsg
      && this.errors[i].fmtMsg == msg.fmtMsg)
      return;
  }

  this.errors.push(msg);
}

ErrorHandler.prototype.printErrors = function(options) {
  console.log(this.getErrors(options).join('\n'));
}

ErrorHandler.prototype.getErrors = function(options) {
  var self = this;
  var showHint = (options && options.hint) ? true : false;

  if (showHint) {
    this.errors.map(function(e) {
      if (!hintSpec.hasOwnProperty(e.code))
        return;

      var hintArray = hintSpec[e.code];
      for (var i in hintArray) {
        var flag = true;
        for (var j in hintArray[i].match) {
          var r = new RegExp(hintArray[i].match[j]);
          flag = flag && r.test(e.args[j]);
        }

        if (flag) { //matching this time.
          e.hint = hintArray[i].hint;

          //formatting the hint message.
          if (Array.isArray(e.args)) {
            for (var j in e.args) {
              var exp = new RegExp('%'+j, "g");
              e.hint = e.hint.replace(exp, e.args[j]);
            }
          }

          //formatting special words such as '{CSS/<display>}'
          var targets = e.hint.match('{.+}');
          for (var j in targets) {
            var element = targets[0];
            var division = element.replace('{','').replace('}','').split('/');

            if ( division[0] === 'CSS') {
              var replacement = '';

              if (CssPropertySpec.hasOwnProperty(division[1])) {
                replacement = CssPropertySpec[division[1]].dataType;
              } else if (CssPrimitiveSpec.hasOwnProperty(division[1])) {
                replacement = ( CssPrimitiveSpec[division[1]].isRegex
                                ? CssPrimitiveSpec[division[1]].regex
                                : CssPrimitiveSpec[division[1]].white);
              } else {
                throw new Error('Invalid synxtax. no such string in CSS');
              }

              e.hint = e.hint.replace(element, replacement);
            }

            if (division[0] === 'HTML') {
              if (division[1] === 'element') {
                var replacement = HtmlSpec[division[1]].tag[division[2]].white;
                if (replacement.length === 1) {
                  e.hint = e.hint.replace('attributes', 'attribute');
                  e.hint = e.hint.replace('are', 'is');
                  e.hint = e.hint.replace(element, replacement);
                } else
                  e.hint = e.hint.replace(element, replacement.join('|'));
              } else if (division[1] === 'global_attribute') {
                if (division.length === 4) {
                  var replacement = HtmlSpec[division[1]][division[2]].attribute[division[3]].join('|');
                  e.hint = e.hint.replace(element, replacement);
                } else if (division.length === 3) {
                  var replacement = HtmlSpec[division[1]][division[2]].white.join('|');
                  e.hint = e.hint.replace(element, replacement);
                }
              } else if (division[1] === 'attribute') {
                var replacement = HtmlSpec.element.tag[division[2]].attribute[division[3]];
                if (replacement.length === 1) {
                  e.hint = e.hint.replace('values', 'value');
                  e.hint = e.hint.replace('are', 'is');
                  e.hint = e.hint.replace(element, replacement);
                } else
                  e.hint = e.hint.replace(element, replacement.join('|'));
              }
            }

            if (division[0] === 'RESOURCE') {
              var replacement = self.profileConfig[division[1]];
              e.hint = e.hint.replace(element, replacement.join('|'));
            }
          }
          return;
        }
      }
    });
  }

  //sort
  this.errors.sort(function(a,b){
    if (a.filePath < b.filePath)
      return  1;
    if (a.filePath > b.filePath)
      return -1;

    // then, line.
    if (a.line < b.line)
      return -1;
    if (a.line > b.line)
      return  1;

    // then, col
    if (a.col < b.col)
      return -1;
    if (a.col > b.col)
      return  1;

    // then, levelMsg
    if (a.levelMsg < b.levelMsg)
      return  1;
    if (a.levelMsg > b.levelMsg)
      return -1;

    //then, fmtMsg
    if (a.fmtMsg < b.fmtMsg)
      return -1;
    if (a.fmtMsg > b.fmtMsg)
      return +1;
    return 0;
  });

  //generate plain messages.
  var list = [];
  for (var i in this.errors) {
    var m = this.errors[i].filePath
      + ':' + this.errors[i].line
      + ':' + this.errors[i].col
      + ' ' + this.errors[i].levelMsg
      + ' ' + this.errors[i].fmtMsg;

    if (showHint && this.errors[i].hint !== '')
      m += ' ' + this.errors[i].hint + '';

    list.push(m);
  }

  return list;
}

ErrorHandler.prototype.clearErrors = function() {
  this.errors = [];
}

module.exports = ErrorHandler;
