var fs = require('fs');
var parser = require('./parse5');

var path = require('path');
var sizeOf = require('image-size');
var utils = require('../lib/utils');

var htmlspec = require('../data/html_spec.json');
var resourceSpec = require('../data/resource_spec.json');

var debug = require('debug')('HtmlValidator');

var HtmlValidator = function(errorHandler) {
  if (!(this instanceof HtmlValidator))
    return new HtmlValidator(errorHandler);

  this.errorHandler = errorHandler;
  this.basePath = null;
  this.fileName = null;
  this.localePath = null;
  this.profileConfig = null;
  this.totalSize = 0;
  this.options = {
    'profile': 'wearable',
    'lang': null
  };
  this.result = {
    'css': [],
    'js': []
  };
}

HtmlValidator.prototype.printError = function(code, loc, args) {
  if (Array.isArray(args)) {
    args.forEach(function(element,index,array){
      array[index] = String(array[index]).replace(/\r|\n/g, '\\n');
    })
  }

  if (this.options.lang !== null)
    this.errorHandler.printError(code, path.join(this.localePath, this.fileName), loc.line, loc.col, args);
  else
    this.errorHandler.printError(code, this.fileName, loc.line, loc.col, args);
}

HtmlValidator.prototype.getErrors = function(code, loc, args) {
  return this.errorHandler.getErrors();
}

HtmlValidator.prototype.validTag = function(tag) {
  if (htmlspec.element.white.indexOf(tag.tagName) !== -1)
    return true;

  return false;
}

HtmlValidator.prototype.useProtocol = function(url, loc) {
  var result = require('url').parse(url);
  if (result.protocol !== null) {
    if (result.protocol === 'http:' || result.protocol === 'https:') {
      this.printError('PERFORMANCE_DISALLOWED_NETWORK_REQUEST', loc, []);
      return true;
    }
    this.printError('PERFORMANCE_INVALID_PROTOCOL', loc, [result.protocol]);
    return true;
  }

  return false;
}

HtmlValidator.prototype.isLocalFile = function(filePath, fileName, loc) {
  if (!utils.isLocalFile(filePath)) {
    this.printError('PERFORMANCE_NO_FILE', loc, [fileName]);
    return false;
  }

  return true;
}

HtmlValidator.prototype.checkAttribute = function(tag) {
  var self = this;

  tag.attrs.map(function(attr) {
    var loc = tag.__location;

    if (htmlspec.global_attribute.common.white.indexOf(attr.name) !== -1
      || htmlspec.global_attribute.event.white.indexOf(attr.name) !== -1) {
        var checkAttrValue = htmlspec.global_attribute.common.attribute[attr.name];
        if (checkAttrValue !== undefined) {
          if (Array.isArray(checkAttrValue)) {
            if (checkAttrValue.indexOf(attr.value.toLowerCase()) === -1)
              self.printError('HTML_INVALID_ATTR_VALUE_IN_TAG', loc, [tag.tagName, attr.name, attr.value]);
          }
        } else {
          if (attr.name === 'style') {
            var cssValue = tag.tagName + ' { ' + attr.value + ' }';
            self.result.css.push ({
              'basePath': self.basePath,
              'filePath': path.join(self.basePath, self.fileName),
              'lang': self.options.lang,
              'loc': tag.__location,
              'text': cssValue
            });
          }
        }
      return;
    }

    var checkAttrs = htmlspec.element.tag[tag.tagName];
    if (checkAttrs === undefined)
      return self.printError('HTML_DISALLOWED_ATTR_IN_TAG', loc, [tag.tagName, attr.name]);

    if (checkAttrs.white.indexOf(attr.name) === -1)
        self.printError('HTML_DISALLOWED_ATTR_IN_TAG', loc, [tag.tagName, attr.name]);
    else {
      var checkAttrValue = checkAttrs.attribute[attr.name];
      if (checkAttrValue !== undefined) {
        if (Array.isArray(checkAttrValue)) {
          if (checkAttrValue.indexOf(attr.value.toLowerCase()) === -1)
            self.printError('HTML_INVALID_ATTR_VALUE_IN_TAG', loc, [tag.tagName, attr.name, attr.value]);
        }
      } else {
        if (tag.tagName === 'link' && attr.name === 'href') {
          if (self.useProtocol(attr.value, loc))
            return;

          if (path.extname(attr.value) !== '.css')
            return;

          var filePath = path.join(self.basePath, attr.value);

          if (self.options.lang !== null)
            filePath = utils.checkLocalePath(self.basePath, attr.value, self.options.lang);

          if (self.isLocalFile(filePath, attr.value, loc)) {

            self.totalSize += utils.getFileSize(filePath);

            self.result.css.push ({
              'basePath': self.basePath,
              'filePath': filePath,
              'lang': self.options.lang,
              'loc': { line: 1, col:0 },
              'text': fs.readFileSync(filePath, 'utf8')
            });
          }
        } else if (tag.tagName === 'img') {
          if (attr.name === 'src') {
            if (self.useProtocol(attr.value, loc))
              return;

            var filePath = path.join(self.basePath, attr.value);

            if (self.options.lang !== null)
              filePath = utils.checkLocalePath(self.basePath, attr.value, self.options.lang);

            if (self.isLocalFile(filePath, attr.value, loc)) {
              var dimensions = null;
              try {
                dimensions = sizeOf(filePath);
                if (self.profileConfig.SUPPORT_IMAGE.indexOf(dimensions.type) === -1)
                  throw new Error('invalid image type');
              } catch(e) {
                debug(e);
                return self.printError('PERFORMANCE_INVALID_IMAGE_TYPE', loc, [path.basename(filePath)]);
              }

              if (dimensions && (dimensions.width > self.profileConfig.VIEWPORT_MAX_WIDTH
                || dimensions.height > self.profileConfig.VIEWPORT_MAX_HEIGHT))
                self.printError('PERFORMANCE_EXCEED_IMAGE_DIMENSION', loc, [path.basename(filePath), dimensions.width, dimensions.height, self.profileConfig.VIEWPORT_MAX_WIDTH, self.profileConfig.VIEWPORT_MAX_HEIGHT, self.options.profile, self.profileConfig.VIEWPORT_WIDTH, self.profileConfig.VIEWPORT_HEIGHT ]);
            }
          } else if (attr.name === 'width' || attr.name === 'height') {
            var pixelReg = '^[\\d]+\\s*(px)?$';
            var result = attr.value.toLowerCase().match(pixelReg);

            if (result !== null) {
              var value;

              if (result[1] == 'px')
                value = result[0].slice(0, result[0].length - 2);
              else
                value = result[0];

              if (attr.name === 'width') {
                if (parseInt(value) > self.profileConfig.VIEWPORT_MAX_WIDTH)
                  self.printError('PERFORMANCE_INVALID_IMAGE_SIZE_VALUE', loc, ['width', value, self.profileConfig.VIEWPORT_MAX_WIDTH]);
              } else {
                if (parseInt(value) > self.profileConfig.VIEWPORT_MAX_HEIGHT)
                  self.printError('PERFORMANCE_INVALID_IMAGE_SIZE_VALUE', loc, ['height', value, self.profileConfig.VIEWPORT_MAX_HEIGHT]);
              }
            } else
              self.printError('HTML_INVALID_ATTR_VALUE_IN_TAG', loc, [tag.tagName, attr.name, attr.value]);
          }
        } else if (tag.tagName === 'script' && attr.name === 'src') {
          if (self.useProtocol(attr.value, loc))
            return;

          var filePath = path.join(self.basePath, attr.value);

          if (self.options.lang !== null)
            filePath = utils.checkLocalePath(self.basePath, attr.value, self.options.lang);

          if (self.isLocalFile(filePath, attr.value, loc)) {
            if (path.extname(attr.value) !== '.js')
              return;

            self.totalSize += utils.getFileSize(filePath);

            self.result.js.push ({
              'fileName': path.relative(self.basePath, filePath),
              'loc': { line: 1, col:0 },
              'text': fs.readFileSync(filePath, 'utf8')
            });
          }
        }
      }
    }
  });
}

HtmlValidator.prototype.checkTags = function(tags) {
  var self = this;

  tags.childNodes.map(function(tag) {
    if (tag.tagName === undefined) {
      if (tag.nodeName === "#documentType") {
        if (tag.name === 'html') {
          if (tag.publicId !== null || tag.systemId !== null || tag.parentNode.quirksMode)
            self.printError('HTML_INVALID_DOCTYPE', tag.__location);
        } else
          self.printError('HTML_INVALID_DOCTYPE', tag.__location);
      }
      return;
    }

    if (self.validTag(tag)) {
      if (tag.attrs.length !== 0)
        self.checkAttribute(tag);

      switch (tag.tagName) {
        case 'script':
          tag.childNodes.map(function(child) {
              if (child.nodeName === '#text')
                self.result.js.push ({
                  'fileName': self.fileName,
                  'loc': tag.__location,
                  'text': child.value
                });
          });
          return;
        case 'style':
          tag.childNodes.map(function(child) {
            if (child.nodeName === '#text')
              self.result.css.push ({
                'basePath': self.basePath,
                'filePath': path.join(self.basePath, self.fileName),
                'lang': self.options.lang,
                'loc': tag.__location,
                'text': child.value
              });
          });
          return;
      }
    } else {
      if (tag.__location === null)
        return self.checkTags(tag);

      self.printError('HTML_DISALLOWED_TAG', tag.__location, [tag.tagName]);
    }

    self.checkTags(tag);
  });
}

HtmlValidator.prototype.validate = function(filePath, options) {
  this.basePath = path.dirname(filePath);
  this.fileName = path.basename(filePath);

  this.totalSize = 0;
  this.result = {
    'css': [],
    'js': []
  };

  this.options = options || this.options;
  this.profileConfig = resourceSpec[this.options.profile];
  this.errorHandler.profileConfig = this.profileConfig;

  if (this.options.lang !== null) {
    filePath = utils.checkLocalePath(this.basePath, this.fileName, this.options.lang);
    this.localePath = path.dirname(path.relative(this.basePath, filePath));
  }

  var contents = fs.readFileSync(filePath, 'utf8');

  this.totalSize += utils.getFileSize(filePath);

  var tags = parser.parse(contents, { locationInfo: true });
  this.checkTags(tags);

  if (this.totalSize > this.profileConfig.LIMITED_TOTAL_SIZE)
    this.printError('PERFORMANCE_EXCEED_RESOURCES_SIZE', {line:0, col:0}
      ,[(this.totalSize / 1024).toFixed(2)
      , (this.profileConfig.LIMITED_TOTAL_SIZE / 1024).toFixed(2)]);

  return this.result;
}

module.exports = HtmlValidator;
