// CodeMirror, copyright © by Marijn Haverbeke and others // Distributed under an MIT license: codemirror.net/LICENSE

(function(mod) {

if (typeof exports == 'object' && typeof module == 'object') { // CommonJS
  mod(require('../../lib/codemirror'));
} else if (typeof define == 'function' && define.amd) { // AMD
  define(['../../lib/codemirror'], mod);
} else { // Plain browser env
  mod(CodeMirror);
}

})(function(CodeMirror) { 'use strict';

var TOKEN_STYLES = {

addition: 'positive',
attributes: 'attribute',
bold: 'strong',
cite: 'keyword',
code: 'atom',
definitionList: 'number',
deletion: 'negative',
div: 'punctuation',
em: 'em',
footnote: 'variable',
footCite: 'qualifier',
header: 'header',
html: 'comment',
image: 'string',
italic: 'em',
link: 'link',
linkDefinition: 'link',
list1: 'variable-2',
list2: 'variable-3',
list3: 'keyword',
notextile: 'string-2',
pre: 'operator',
p: 'property',
quote: 'bracket',
span: 'quote',
specialChar: 'tag',
strong: 'strong',
sub: 'builtin',
sup: 'builtin',
table: 'variable-3',
tableHeading: 'operator'

};

function Parser(regExpFactory, state, stream) {

this.regExpFactory = regExpFactory;
this.state = state;
this.stream = stream;
this.styles = TOKEN_STYLES;

this.state.specialChar = null;

}

Parser.prototype.eat = function(name) {

return this.stream.match(this.regExpFactory.pattern(name), true);

};

Parser.prototype.check = function(name) {

return this.stream.match(this.regExpFactory.pattern(name), false);

};

Parser.prototype.setModeForNextToken = function(mode) {

return this.state.mode = mode;

};

Parser.prototype.execMode = function(newMode) {

return this.setModeForNextToken(newMode).call(this);

};

Parser.prototype.startNewLine = function() {

this.setModeForNextToken(Modes.newLayout);
this.state.tableHeading = false;

if (this.state.layoutType === 'definitionList' && this.state.spanningLayout) {
  if (this.check('definitionListEnd')) {
    this.state.spanningLayout = false;
  }
}

};

Parser.prototype.nextToken = function() {

return this.state.mode.call(this);

};

Parser.prototype.styleFor = function(token) {

if (this.styles.hasOwnProperty(token)) {
  return this.styles[token];
}
throw 'unknown token';

};

Parser.prototype.handlePhraseModifier = function(ch) {

if (ch === '_') {
  if (this.stream.eat('_')) {
    return this.togglePhraseModifier('italic', /^.*__/);
  }
  return this.togglePhraseModifier('em', /^.*_/);
}

if (ch === '*') {
  if (this.stream.eat('*')) {
    return this.togglePhraseModifier('bold', /^.*\*\*/);
  }
  return this.togglePhraseModifier('strong', /^.*\*/);
}

if (ch === '[') {
  if (this.stream.match(/\d+\]/)) {
    this.state.footCite = true;
  }
  return this.tokenStyles();
}

if (ch === '(') {
  if (this.stream.match('r)')) {
    this.state.specialChar = 'r';
  } else if (this.stream.match('tm)')) {
    this.state.specialChar = 'tm';
  } else if (this.stream.match('c)')) {
    this.state.specialChar = 'c';
  }
  return this.tokenStyles();
}

if (ch === '<') {
  if (this.stream.match(/(\w+)[^>]+>[^<]+<\/\1>/)) {
    return this.tokenStylesWith(this.styleFor('html'));
  }
}

if (ch === '?' && this.stream.eat('?')) {
  return this.togglePhraseModifier('cite', /^.*\?\?/);
}
if (ch === '=' && this.stream.eat('=')) {
  return this.togglePhraseModifier('notextile', /^.*==/);
}
if (ch === '-') {
  return this.togglePhraseModifier('deletion', /^.*-/);
}
if (ch === '+') {
  return this.togglePhraseModifier('addition', /^.*\+/);
}
if (ch === '~') {
  return this.togglePhraseModifier('sub', /^.*~/);
}
if (ch === '^') {
  return this.togglePhraseModifier('sup', /^.*\^/);
}
if (ch === '%') {
  return this.togglePhraseModifier('span', /^.*%/);
}
if (ch === '@') {
  return this.togglePhraseModifier('code', /^.*@/);
}
if (ch === '!') {
  var type = this.togglePhraseModifier('image', /^.*(?:\([^\)]+\))?!/);
  this.stream.match(/^:\S+/); // optional Url portion
  return type;
}
return this.tokenStyles();

};

Parser.prototype.togglePhraseModifier = function(phraseModifier, closeRE) {

if (this.state[phraseModifier]) { // remove phrase modifier
  var type = this.tokenStyles();
  this.state[phraseModifier] = false;
  return type;
}
if (this.stream.match(closeRE, false)) { // add phrase modifier
  this.state[phraseModifier] = true;
  this.setModeForNextToken(Modes.attributes);
}
return this.tokenStyles();

};

Parser.prototype.tokenStyles = function() {

var disabled = this.textileDisabled(),
    styles = [];

if (disabled) return disabled;

if (this.state.layoutType) {
  styles.push(this.styleFor(this.state.layoutType));
}

styles = styles.concat(this.activeStyles('addition', 'bold', 'cite', 'code',
    'deletion', 'em', 'footCite', 'image', 'italic', 'link', 'span', 'specialChar', 'strong',
    'sub', 'sup', 'table', 'tableHeading'));

if (this.state.layoutType === 'header') {
  styles.push(this.styleFor('header') + '-' + this.state.header);
}
return styles.length ? styles.join(' ') : null;

};

Parser.prototype.textileDisabled = function() {

var type = this.state.layoutType;

switch(type) {
  case 'notextile':
  case 'code':
  case 'pre':
    return this.styleFor(type);
  default:
    if (this.state.notextile) {
      return this.styleFor('notextile') + (type ? (' ' + this.styleFor(type)) : '');
    }

    return null;
}

};

Parser.prototype.tokenStylesWith = function(extraStyles) {

var disabled = this.textileDisabled(),
    type;

if (disabled) return disabled;

type = this.tokenStyles();
if(extraStyles) {
  return type ? (type + ' ' + extraStyles) : extraStyles;
}
return type;

};

Parser.prototype.activeStyles = function() {

var styles = [],
    i;
for (i = 0; i < arguments.length; ++i) {
  if (this.state[arguments[i]]) {
    styles.push(this.styleFor(arguments[i]));
  }
}
return styles;

};

Parser.prototype.blankLine = function() {

var spanningLayout = this.state.spanningLayout,
    type = this.state.layoutType,
    key;

for (key in this.state) {
  if (this.state.hasOwnProperty(key)) {
    delete this.state[key];
  }
}

this.setModeForNextToken(Modes.newLayout);
if (spanningLayout) {
  this.state.layoutType = type;
  this.state.spanningLayout = true;
}

};

function RegExpFactory() {

this.cache = {};
this.single = {
  bc: 'bc',
  bq: 'bq',
  definitionList: /- [^(?::=)]+:=+/,
  definitionListEnd: /.*=:\s*$/,
  div: 'div',
  drawTable: /\|.*\|/,
  foot: /fn\d+/,
  header: /h[1-6]/,
  html: /\s*<(?:\/)?(\w+)(?:[^>]+)?>(?:[^<]+<\/\1>)?/,
  link: /[^"]+":\S/,
  linkDefinition: /\[[^\s\]]+\]\S+/,
  list: /(?:#+|\*+)/,
  notextile: 'notextile',
  para: 'p',
  pre: 'pre',
  table: 'table',
  tableCellAttributes: /[/\\]\d+/,
  tableHeading: /\|_\./,
  tableText: /[^"_\*\[\(\?\+~\^%@|-]+/,
  text: /[^!"_=\*\[\(<\?\+~\^%@-]+/
};
this.attributes = {
  align: /(?:<>|<|>|=)/,
  selector: /\([^\(][^\)]+\)/,
  lang: /\[[^\[\]]+\]/,
  pad: /(?:\(+|\)+){1,2}/,
  css: /\{[^\}]+\}/
};

}

RegExpFactory.prototype.pattern = function(name) {

return (this.cache[name] || this.createRe(name));

};

RegExpFactory.prototype.createRe = function(name) {

switch (name) {
  case 'drawTable':
    return this.makeRe('^', this.single.drawTable, '$');
  case 'html':
    return this.makeRe('^', this.single.html, '(?:', this.single.html, ')*', '$');
  case 'linkDefinition':
    return this.makeRe('^', this.single.linkDefinition, '$');
  case 'listLayout':
    return this.makeRe('^', this.single.list, this.pattern('allAttributes'), '*\\s+');
  case 'tableCellAttributes':
    return this.makeRe('^', this.choiceRe(this.single.tableCellAttributes,
        this.pattern('allAttributes')), '+\\.');
  case 'type':
    return this.makeRe('^', this.pattern('allTypes'));
  case 'typeLayout':
    return this.makeRe('^', this.pattern('allTypes'), this.pattern('allAttributes'),
        '*\\.\\.?', '(\\s+|$)');
  case 'attributes':
    return this.makeRe('^', this.pattern('allAttributes'), '+');

  case 'allTypes':
    return this.choiceRe(this.single.div, this.single.foot,
        this.single.header, this.single.bc, this.single.bq,
        this.single.notextile, this.single.pre, this.single.table,
        this.single.para);

  case 'allAttributes':
    return this.choiceRe(this.attributes.selector, this.attributes.css,
        this.attributes.lang, this.attributes.align, this.attributes.pad);

  default:
    return this.makeRe('^', this.single[name]);
}

};

RegExpFactory.prototype.makeRe = function() {

var pattern = '',
    i,
    arg;

for (i = 0; i < arguments.length; ++i) {
  arg = arguments[i];
  pattern += (typeof arg === 'string') ? arg : arg.source;
}
return new RegExp(pattern);

};

RegExpFactory.prototype.choiceRe = function() {

var parts = [arguments[0]],
    i;

for (i = 1; i < arguments.length; ++i) {
  parts[i * 2 - 1] = '|';
  parts[i * 2] = arguments[i];
}

parts.unshift('(?:');
parts.push(')');
return this.makeRe.apply(this, parts);

};

var Modes = {

newLayout: function() {
  if (this.check('typeLayout')) {
    this.state.spanningLayout = false;
    return this.execMode(Modes.blockType);
  }
  if (!this.textileDisabled()) {
    if (this.check('listLayout')) {
      return this.execMode(Modes.list);
    } else if (this.check('drawTable')) {
      return this.execMode(Modes.table);
    } else if (this.check('linkDefinition')) {
      return this.execMode(Modes.linkDefinition);
    } else if (this.check('definitionList')) {
      return this.execMode(Modes.definitionList);
    } else if (this.check('html')) {
      return this.execMode(Modes.html);
    }
  }
  return this.execMode(Modes.text);
},

blockType: function() {
  var match,
      type;
  this.state.layoutType = null;

  if (match = this.eat('type')) {
    type = match[0];
  } else {
    return this.execMode(Modes.text);
  }

  if(match = type.match(this.regExpFactory.pattern('header'))) {
    this.state.layoutType = 'header';
    this.state.header = parseInt(match[0][1]);
  } else if (type.match(this.regExpFactory.pattern('bq'))) {
    this.state.layoutType = 'quote';
  } else if (type.match(this.regExpFactory.pattern('bc'))) {
    this.state.layoutType = 'code';
  } else if (type.match(this.regExpFactory.pattern('foot'))) {
    this.state.layoutType = 'footnote';
  } else if (type.match(this.regExpFactory.pattern('notextile'))) {
    this.state.layoutType = 'notextile';
  } else if (type.match(this.regExpFactory.pattern('pre'))) {
    this.state.layoutType = 'pre';
  } else if (type.match(this.regExpFactory.pattern('div'))) {
    this.state.layoutType = 'div';
  } else if (type.match(this.regExpFactory.pattern('table'))) {
    this.state.layoutType = 'table';
  }

  this.setModeForNextToken(Modes.attributes);
  return this.tokenStyles();
},

text: function() {
  if (this.eat('text')) {
    return this.tokenStyles();
  }

  var ch = this.stream.next();

  if (ch === '"') {
    return this.execMode(Modes.link);
  }
  return this.handlePhraseModifier(ch);
},

attributes: function() {
  this.setModeForNextToken(Modes.layoutLength);

  if (this.eat('attributes')) {
    return this.tokenStylesWith(this.styleFor('attributes'));
  }
  return this.tokenStyles();
},

layoutLength: function() {
  if (this.stream.eat('.') && this.stream.eat('.')) {
    this.state.spanningLayout = true;
  }

  this.setModeForNextToken(Modes.text);
  return this.tokenStyles();
},

list: function() {
  var match = this.eat('list'),
      listMod;
  this.state.listDepth = match[0].length;
  listMod = (this.state.listDepth - 1) % 3;
  if (!listMod) {
    this.state.layoutType = 'list1';
  } else if (listMod === 1) {
    this.state.layoutType = 'list2';
  } else {
    this.state.layoutType = 'list3';
  }
  this.setModeForNextToken(Modes.attributes);
  return this.tokenStyles();
},

link: function() {
  this.setModeForNextToken(Modes.text);
  if (this.eat('link')) {
    this.stream.match(/\S+/);
    return this.tokenStylesWith(this.styleFor('link'));
  }
  return this.tokenStyles();
},

linkDefinition: function() {
  this.stream.skipToEnd();
  return this.tokenStylesWith(this.styleFor('linkDefinition'));
},

definitionList: function() {
  this.eat('definitionList');

  this.state.layoutType = 'definitionList';

  if (this.stream.match(/\s*$/)) {
    this.state.spanningLayout = true;
  } else {
    this.setModeForNextToken(Modes.attributes);
  }
  return this.tokenStyles();
},

html: function() {
  this.stream.skipToEnd();
  return this.tokenStylesWith(this.styleFor('html'));
},

table: function() {
  this.state.layoutType = 'table';
  return this.execMode(Modes.tableCell);
},

tableCell: function() {
  if (this.eat('tableHeading')) {
    this.state.tableHeading = true;
  } else {
    this.stream.eat('|');
  }
  this.setModeForNextToken(Modes.tableCellAttributes);
  return this.tokenStyles();
},

tableCellAttributes: function() {
  this.setModeForNextToken(Modes.tableText);

  if (this.eat('tableCellAttributes')) {
    return this.tokenStylesWith(this.styleFor('attributes'));
  }
  return this.tokenStyles();
},

tableText: function() {
  if (this.eat('tableText')) {
    return this.tokenStyles();
  }

  if (this.stream.peek() === '|') { // end of cell
    this.setModeForNextToken(Modes.tableCell);
    return this.tokenStyles();
  }
  return this.handlePhraseModifier(this.stream.next());
}

};

CodeMirror.defineMode('textile', function() {

var regExpFactory = new RegExpFactory();

return {
  startState: function() {
    return { mode: Modes.newLayout };
  },
  token: function(stream, state) {
    var parser = new Parser(regExpFactory, state, stream);
    if (stream.sol()) { parser.startNewLine(); }
    return parser.nextToken();
  },
  blankLine: function(state) {
    new Parser(regExpFactory, state).blankLine();
  }
};

});

CodeMirror.defineMIME('text/x-textile', 'textile'); });