'use strict';

var MetadataExtractor = require('./MetadataExtractor');
/*

  Uniforms you will need to set, in addition to any inputs specified are
  RENDERSIZE: vec2 rendering size in pixels
  TIME: float time in seconds since rendering started
  PASSINDEX: int index of the current pass being rendered
  See http://vdmx.vidvox.net/blog/isf for more info

*/

var typeUniformMap = {
  float: 'float',
  image: 'sampler2D',
  bool: 'bool',
  event: 'bool',
  long: 'int',
  color: 'vec4',
  point2D: 'vec2'
};

var ISFParser = function ISFParser() {};

ISFParser.prototype.parse = function parse(rawFragmentShader, rawVertexShader) {
  try {
    this.valid = true;
    this.rawFragmentShader = rawFragmentShader;
    this.rawVertexShader = rawVertexShader || ISFParser.vertexShaderDefault;
    this.error = null;
    var metadataInfo = MetadataExtractor(this.rawFragmentShader);
    var metadata = metadataInfo.objectValue;
    var metadataString = metadataInfo.stringValue;
    this.metadata = metadata;
    this.credit = metadata.CREDIT;
    this.categories = metadata.CATEGORIES;
    this.inputs = metadata.INPUTS;
    this.imports = metadata.IMPORTED || {};
    this.description = metadata.DESCRIPTION;

    var passesArray = metadata.PASSES || [{}];
    this.passes = this.parsePasses(passesArray);
    var endOfMetadata = this.rawFragmentShader.indexOf(metadataString) + metadataString.length + 2;
    this.rawFragmentMain = this.rawFragmentShader.substring(endOfMetadata);
    this.generateShaders();
    this.inferFilterType();
    this.isfVersion = this.inferISFVersion();
  } catch (e) {
    this.valid = false;
    this.error = e;
    this.inputs = [];
    this.categories = [];
    this.credit = '';
    this.errorLine = e.lineNumber;
  }
};

ISFParser.prototype.parsePasses = function parsePasses(passesArray) {
  var passes = [];
  for (var i = 0; i < passesArray.length; ++i) {
    var passDefinition = passesArray[i];
    var pass = {};
    if (passDefinition.TARGET) pass.target = passDefinition.TARGET;
    pass.persistent = !!passDefinition.PERSISTENT;
    pass.width = passDefinition.WIDTH || '$WIDTH';
    pass.height = passDefinition.HEIGHT || '$HEIGHT';
    pass.float = !!passDefinition.FLOAT;
    passes.push(pass);
  }
  return passes;
};

ISFParser.prototype.generateShaders = function generateShaders() {
  this.uniformDefs = '';
  for (var i = 0; i < this.inputs.length; ++i) {
    this.addUniform(this.inputs[i]);
  }

  for (var _i = 0; _i < this.passes.length; ++_i) {
    if (this.passes[_i].target) {
      this.addUniform({ NAME: this.passes[_i].target, TYPE: 'image' });
    }
  }

  for (var k in this.imports) {
    if ({}.hasOwnProperty.call(this.imports, k)) {
      this.addUniform({ NAME: k, TYPE: 'image' });
    }
  }

  this.fragmentShader = this.buildFragmentShader();
  this.vertexShader = this.buildVertexShader();
};

ISFParser.prototype.addUniform = function addUniform(input) {
  var type = this.inputToType(input.TYPE);
  this.addUniformLine('uniform ' + type + ' ' + input.NAME + ';');
  if (type === 'sampler2D') {
    this.addUniformLine(this.samplerUniforms(input));
  }
};

ISFParser.prototype.addUniformLine = function addUniformLine(line) {
  this.uniformDefs += line + '\n';
};

ISFParser.prototype.samplerUniforms = function samplerUniforms(input) {
  var name = input.NAME;
  var lines = '';
  lines += 'uniform vec4 _' + name + '_imgRect;\n';
  lines += 'uniform vec2 _' + name + '_imgSize;\n';
  lines += 'uniform bool _' + name + '_flip;\n';
  lines += 'varying vec2 _' + name + '_normTexCoord;\n';
  lines += 'varying vec2 _' + name + '_texCoord;\n';
  lines += '\n';
  return lines;
};

ISFParser.prototype.buildFragmentShader = function buildFragmentShader() {
  var main = this.replaceSpecialFunctions(this.rawFragmentMain);
  return ISFParser.fragmentShaderSkeleton.replace('[[uniforms]]', this.uniformDefs).replace('[[main]]', main);
};

ISFParser.prototype.replaceSpecialFunctions = function replaceSpecialFunctions(source) {
  var regex = void 0;

  // IMG_THIS_PIXEL
  regex = /IMG_THIS_PIXEL\((.+?)\)/g;
  source = source.replace(regex, function (fullMatch, innerMatch) {
    return 'texture2D(' + innerMatch + ', isf_FragNormCoord)';
  });

  // IMG_THIS_NORM_PIXEL
  regex = /IMG_THIS_NORM_PIXEL\((.+?)\)/g;
  source = source.replace(regex, function (fullMatch, innerMatch) {
    return 'texture2D(' + innerMatch + ', isf_FragNormCoord)';
  });

  // IMG_PIXEL
  regex = /IMG_PIXEL\((.+?)\)/g;
  source = source.replace(regex, function (fullMatch, innerMatch) {
    var results = innerMatch.split(',');
    var sampler = results[0];
    var coord = results[1];
    return 'texture2D(' + sampler + ', (' + coord + ') / RENDERSIZE)';
  });

  // IMG_NORM_PIXEL
  regex = /IMG_NORM_PIXEL\((.+?)\)/g;
  source = source.replace(regex, function (fullMatch, innerMatch) {
    var results = innerMatch.split(',');
    var sampler = results[0];
    var coord = results[1];
    return 'VVSAMPLER_2DBYNORM(' + sampler + ', _' + sampler + '_imgRect, _' + sampler + '_imgSize, _' + sampler + '_flip, ' + coord + ')';
  });

  // IMG_SIZE
  regex = /IMG_SIZE\((.+?)\)/g;
  source = source.replace(regex, function (fullMatch, imgName) {
    return '_' + imgName + '_imgSize';
  });
  return source;
};

ISFParser.prototype.buildVertexShader = function buildVertexShader() {
  var functionLines = '\n';
  for (var i = 0; i < this.inputs.length; ++i) {
    var input = this.inputs[i];
    if (input.TYPE === 'image') {
      functionLines += this.texCoordFunctions(input) + '\n';
    }
  }
  return ISFParser.vertexShaderSkeleton.replace('[[functions]]', functionLines).replace('[[uniforms]]', this.uniformDefs).replace('[[main]]', this.rawVertexShader);
};

ISFParser.prototype.texCoordFunctions = function texCoordFunctions(input) {
  var name = input.NAME;
  return ['_[[name]]_texCoord =', '    vec2(((isf_fragCoord.x / _[[name]]_imgSize.x * _[[name]]_imgRect.z) + _[[name]]_imgRect.x), ', '          (isf_fragCoord.y / _[[name]]_imgSize.y * _[[name]]_imgRect.w) + _[[name]]_imgRect.y);', '', '_[[name]]_normTexCoord =', '  vec2((((isf_FragNormCoord.x * _[[name]]_imgSize.x) / _[[name]]_imgSize.x * _[[name]]_imgRect.z) + _[[name]]_imgRect.x),', '          ((isf_FragNormCoord.y * _[[name]]_imgSize.y) / _[[name]]_imgSize.y * _[[name]]_imgRect.w) + _[[name]]_imgRect.y);'].join('\n').replace(/\[\[name\]\]/g, name);
};

ISFParser.prototype.inferFilterType = function inferFilterType() {
  function any(arr, test) {
    return arr.filter(test).length > 0;
  }
  var isFilter = any(this.inputs, function (input) {
    return input.TYPE === 'image' && input.NAME === 'inputImage';
  });
  var isTransition = any(this.inputs, function (input) {
    return input.TYPE === 'image' && input.NAME === 'startImage';
  }) && any(this.inputs, function (input) {
    return input.TYPE === 'image' && input.NAME === 'endImage';
  }) && any(this.inputs, function (input) {
    return input.TYPE === 'float' && input.NAME === 'progress';
  });
  if (isFilter) {
    this.type = 'filter';
  } else if (isTransition) {
    this.type = 'transition';
  } else {
    this.type = 'generator';
  }
};

ISFParser.prototype.inferISFVersion = function inferISFVersion() {
  var v = 2;
  if (this.metadata.PERSISTENT_BUFFERS || this.rawFragmentShader.indexOf('vv_FragNormCoord') !== -1 || this.rawVertexShader.indexOf('vv_vertShaderInit') !== -1 || this.rawVertexShader.indexOf('vv_FragNormCoord') !== -1) {
    v = 1;
  }
  return v;
};

ISFParser.prototype.inputToType = function inputToType(inputType) {
  var type = typeUniformMap[inputType];
  if (!type) throw new Error('Unknown input type [' + inputType + ']');
  return type;
};

ISFParser.fragmentShaderSkeleton = '\nprecision highp float;\nprecision highp int;\n\nuniform int PASSINDEX;\nuniform vec2 RENDERSIZE;\nvarying vec2 isf_FragNormCoord;\nvarying vec2 isf_FragCoord;\nuniform float TIME;\nuniform float TIMEDELTA;\nuniform int FRAMEINDEX;\nuniform vec4 DATE;\n\n[[uniforms]]\n\n// We don\'t need 2DRect functions since we control all inputs.  Don\'t need flip either, but leaving\n// for consistency sake.\nvec4 VVSAMPLER_2DBYPIXEL(sampler2D sampler, vec4 samplerImgRect, vec2 samplerImgSize, bool samplerFlip, vec2 loc) {\n  return (samplerFlip)\n    ? texture2D   (sampler,vec2(((loc.x/samplerImgSize.x*samplerImgRect.z)+samplerImgRect.x), (samplerImgRect.w-(loc.y/samplerImgSize.y*samplerImgRect.w)+samplerImgRect.y)))\n    : texture2D   (sampler,vec2(((loc.x/samplerImgSize.x*samplerImgRect.z)+samplerImgRect.x), ((loc.y/samplerImgSize.y*samplerImgRect.w)+samplerImgRect.y)));\n}\nvec4 VVSAMPLER_2DBYNORM(sampler2D sampler, vec4 samplerImgRect, vec2 samplerImgSize, bool samplerFlip, vec2 normLoc)  {\n  vec4    returnMe = VVSAMPLER_2DBYPIXEL(   sampler,samplerImgRect,samplerImgSize,samplerFlip,vec2(normLoc.x*samplerImgSize.x, normLoc.y*samplerImgSize.y));\n  return returnMe;\n}\n\n[[main]]\n\n';

ISFParser.vertexShaderDefault = '\nvoid main() {\n  isf_vertShaderInit();\n}\n';
ISFParser.vertexShaderSkeleton = '\nprecision highp float;\nprecision highp int;\nvoid isf_vertShaderInit();\n\nattribute vec2 position; // -1..1\n\nuniform int     PASSINDEX;\nuniform vec2    RENDERSIZE;\nvarying vec2    isf_FragNormCoord; // 0..1\nvec2    isf_fragCoord; // Pixel Space\n\n[[uniforms]]\n\n[[main]]\nvoid isf_vertShaderInit(void)  {\ngl_Position = vec4( position, 0.0, 1.0 );\n  isf_FragNormCoord = vec2((gl_Position.x+1.0)/2.0, (gl_Position.y+1.0)/2.0);\n  isf_fragCoord = floor(isf_FragNormCoord * RENDERSIZE);\n  [[functions]]\n}\n';

module.exports = ISFParser;