/* -*- Mode: Javascript; indent-tabs-mode:nil; js-indent-level: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */

/*************************************************************
 *
 *  MathJax/extensions/TeX/mhchem.js
 *  
 *  Implements the \ce command for handling chemical formulas
 *  from the mhchem LaTeX package.
 *  
 *  ---------------------------------------------------------------------
 *  
 *  Copyright (c) 2011-2015 The MathJax Consortium
 * 
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

MathJax.Extension["TeX/mhchem"] = {
  version: "2.6.0"
};

MathJax.Hub.Register.StartupHook("TeX Jax Ready",function () {
  
  var TEX = MathJax.InputJax.TeX;
  
  /*
   *  This is the main class for handing the \ce and related commands.
   *  Its main method is Parse() which takes the argument to \ce and
   *  returns the corresponding TeX string.
   */

  var CE = MathJax.Object.Subclass({
    string: "",   // the \ce string being parsed
    i: 0,         // the current position in the string
    tex: "",      // the partially processed TeX result
    TEX: "",      // the full TeX result
    atom: false,  // last processed token is an atom
    sup: "",      // pending superscript
    sub: "",      // pending subscript
    presup: "",   // pending pre-superscript
    presub: "",   // pending pre-subscript
    
    //
    //  Store the string when a CE object is created
    //
    Init: function (string) {this.string = string},
    
    //
    //  These are the special characters and the methods that
    //  handle them.  All others are passed through verbatim.
    //
    ParseTable: {
      '-': "Minus",
      '+': "Plus",
      '(': "Open",
      ')': "Close",
      '[': "Open",
      ']': "Close",
      '<': "Less",
      '^': "Superscript",
      '_': "Subscript",
      '*': "Dot",
      '.': "Dot",
      '=': "Equal",
      '#': "Pound",
      '$': "Math",
      '\\': "Macro",
      ' ': "Space"
    },
    //
    //  Basic arrow names for reactions
    //
    Arrows: {
      '->': "rightarrow",
      '<-': "leftarrow",
      '<->': "leftrightarrow",
      '<=>': "rightleftharpoons",
      '<=>>': "Rightleftharpoons",
      '<<=>': "Leftrightharpoons",
      '^': "uparrow",
      'v': "downarrow"
    },
    
    //
    //  Implementations for the various bonds
    //  (the ~ ones are hacks that don't work well in NativeMML)
    //
    Bonds: {
      '-': "-",
      '=': "=",
      '#': "\\equiv",
      '~': "\\tripledash",
      '~-': "\\begin{CEstack}{}\\tripledash\\\\-\\end{CEstack}",
      '~=': "\\raise2mu{\\begin{CEstack}{}\\tripledash\\\\-\\\\-\\end{CEstack}}",
      '~--': "\\raise2mu{\\begin{CEstack}{}\\tripledash\\\\-\\\\-\\end{CEstack}}",
      '-~-': "\\raise2mu{\\begin{CEstack}{}-\\\\\\tripledash\\\\-\\end{CEstack}}",
      '...': "{\\cdot}{\\cdot}{\\cdot}",
      '....': "{\\cdot}{\\cdot}{\\cdot}{\\cdot}",
      '->': "\\rightarrow",
      '<-': "\\leftarrow",
      '??': "\\text{??}"           // unknown bond
    },

    //
    //  This converts the CE string to a TeX string.
    //  It loops through the string and calls the proper
    //  method depending on the ccurrent character.
    //  
    Parse: function () {
      this.tex = ""; this.atom = false;
      while (this.i < this.string.length) {
        var c = this.string.charAt(this.i);
        if (c.match(/[a-z]/i)) {this.ParseLetter()}
        else if (c.match(/[0-9]/)) {this.ParseNumber()}
        else {this["Parse"+(this.ParseTable[c]||"Other")](c)}
      }
      this.FinishAtom(true);
      return this.TEX;
    },
    
    //
    //  Make an atom name or a down arrow
    //  
    ParseLetter: function () {
      this.FinishAtom();
      if (this.Match(/^v( |$)/)) {
        this.tex += "{\\"+this.Arrows["v"]+"}";
      } else {
        this.tex += "\\text{"+this.Match(/^[a-z]+/i)+"}";
        this.atom = true;
      }
    },
    
    //
    //  Make a number or fraction preceeding an atom,
    //  or a subscript for an atom.
    //  
    ParseNumber: function () {
      var n = this.Match(/^\d+/);
      if (this.atom && !this.sub) {
        this.sub = n;
      } else {
        this.FinishAtom();
        var match = this.Match(/^\/\d+/);
        if (match) {
          var frac = "\\frac{"+n+"}{"+match.substr(1)+"}";
          this.tex += "\\mathchoice{\\textstyle"+frac+"}{"+frac+"}{"+frac+"}{"+frac+"}";
        } else {
          this.tex += n;
          if (this.i < this.string.length) {this.tex += "\\,"}
        }
      }
    },
    
    //
    //  Make a superscript minus, or an arrow, or a single bond.
    //
    ParseMinus: function (c) {
      if (this.atom && (this.i === this.string.length-1 || this.string.charAt(this.i+1) === " ")) {
        this.sup += c;
      } else {
        this.FinishAtom();
        if (this.string.substr(this.i,2) === "->") {this.i += 2; this.AddArrow("->"); return}
        else {this.tex += "{-}"}
      }
      this.i++;
    },

    //
    //  Make a superscript plus, or pass it through
    //
    ParsePlus: function (c) {
      if (this.atom) {this.sup += c} else {this.FinishAtom(); this.tex += c}
      this.i++;
    },
    
    //
    //  Handle dots and double or triple bonds
    //
    ParseDot:   function (c) {this.FinishAtom(); this.tex += "\\cdot "; this.i++},
    ParseEqual: function (c) {this.FinishAtom(); this.tex += "{=}"; this.i++},
    ParsePound: function (c) {this.FinishAtom(); this.tex += "{\\equiv}"; this.i++},

    //
    //  Look for (v) or (^), or pass it through
    //
    ParseOpen: function (c) {
      this.FinishAtom();
      var match = this.Match(/^\([v^]\)/);
      if (match) {this.tex += "{\\"+this.Arrows[match.charAt(1)]+"}"}
        else {this.tex += "{"+c; this.i++}
    },
    //
    //  Allow ) and ] to get super- and subscripts
    //
    ParseClose: function (c) {this.FinishAtom(); this.atom = true; this.tex += c+"}"; this.i++},

    //
    //  Make the proper arrow
    //
    ParseLess: function (c) {
      this.FinishAtom();
      var arrow = this.Match(/^(<->?|<=>>?|<<=>)/);
      if (!arrow) {this.tex += c; this.i++} else {this.AddArrow(arrow)}
    },

    //
    //  Look for a superscript, or an up arrow
    //  
    ParseSuperscript: function (c) {
      c = this.string.charAt(++this.i);
      if (c === "{") {
        this.i++; var m = this.Find("}");
        if (m === "-.") {this.sup += "{-}{\\cdot}"}
        else if (m) {this.sup += CE(m).Parse().replace(/^\{-\}/,"-")}
      } else if (c === " " || c === "") {
        this.tex += "{\\"+this.Arrows["^"]+"}"; this.i++;
      } else {
        var n = this.Match(/^(\d+|-\.)/);
        if (n) {this.sup += n}
      }
    },
    //
    //  Look for subscripts
    //
    ParseSubscript: function (c) {
      if (this.string.charAt(++this.i) == "{") {
        this.i++; this.sub += CE(this.Find("}")).Parse().replace(/^\{-\}/,"-");
      } else {
        var n = this.Match(/^\d+/);
        if (n) {this.sub += n}
      }
    },

    //
    //  Look for raw TeX code to include
    //
    ParseMath: function (c) {
      this.FinishAtom();
      this.i++; this.tex += this.Find(c);
    },
    
    //
    //  Look for specific macros for bonds
    //  and allow \} to have subscripts
    //
    ParseMacro: function (c) {
      this.FinishAtom();
      this.i++; var match = this.Match(/^([a-z]+|.)/i)||" ";
      if (match === "sbond") {this.tex += "{-}"}
      else if (match === "dbond") {this.tex += "{=}"}
      else if (match === "tbond") {this.tex += "{\\equiv}"}
      else if (match === "bond") {
        var bond = (this.Match(/^\{.*?\}/)||"");
        bond = bond.substr(1,bond.length-2);
        this.tex += "{"+(this.Bonds[bond]||"\\text{??}")+"}";
      }
      else if (match === "{") {this.tex += "{\\{"}
      else if (match === "}") {this.tex += "\\}}"; this.atom = true}
      else {this.tex += c+match}
    },
    
    //
    //  Ignore spaces
    //
    ParseSpace: function (c) {this.FinishAtom(); this.i++},
    
    //
    //  Pass anything else on verbatim
    //
    ParseOther: function (c) {this.FinishAtom(); this.tex += c; this.i++},

    //
    //  Process an arrow (looking for brackets for above and below)
    //
    AddArrow: function (arrow) {
      var c = this.Match(/^[CT]\[/);
      if (c) {this.i--; c = c.charAt(0)}
      var above = this.GetBracket(c), below = this.GetBracket(c);
      arrow = this.Arrows[arrow];
      if (above || below) {
        if (below) {arrow += "["+below+"]"}
        arrow += "{"+above+"}";
        arrow = "\\mathrel{\\x"+arrow+"}";
      } else {
        arrow = "\\long"+arrow+" ";
      }
      this.tex += arrow;
    },

    //
    //  Handle the super and subscripts for an atom
    //  
    FinishAtom: function (force) {
      if (this.sup || this.sub || this.presup || this.presub) {
        if (!force && !this.atom) {
          if (this.tex === "" && !this.sup && !this.sub) return;
          if (!this.presup && !this.presub &&
                (this.tex === "" || this.tex === "{" ||
                (this.tex === "}" && this.TEX.substr(-1) === "{"))) {
            this.presup = this.sup, this.presub = this.sub;  // save for later
            this.sub = this.sup = "";
            this.TEX += this.tex; this.tex = "";
            return;
          }
        }
        if (this.sub && !this.sup) {this.sup = "\\Space{0pt}{0pt}{.2em}"} // forces subscripts to align properly
        if ((this.presup || this.presub) && this.tex !== "{") {
          if (!this.presup && !this.sup) {this.presup = "\\Space{0pt}{0pt}{.2em}"}
          this.tex = "\\CEprescripts{"+(this.presub||"\\CEnone")+"}{"+(this.presup||"\\CEnone")+"}"
                   + "{"+(this.tex !== "}" ? this.tex : "")+"}"
                   + "{"+(this.sub||"\\CEnone")+"}{"+(this.sup||"\\CEnone")+"}"
                   + (this.tex === "}" ? "}" : "");
          this.presub = this.presup = "";
        } else {
          if (this.sup) this.tex += "^{"+this.sup+"}";
          if (this.sub) this.tex += "_{"+this.sub+"}";
        }
        this.sup = this.sub = "";
      }
      this.TEX += this.tex; this.tex = "";
      this.atom = false;
    },
    
    //
    //  Find a bracket group and handle C and T prefixes
    //
    GetBracket: function (c) {
      if (this.string.charAt(this.i) !== "[") {return ""}
      this.i++; var bracket = this.Find("]");
      if (c === "C") {bracket = "\\ce{"+bracket+"}"} else
      if (c === "T") {
        if (!bracket.match(/^\{.*\}$/)) {bracket = "{"+bracket+"}"}
        bracket = "\\text"+bracket;
      };
      return bracket;
    },

    //
    //  Check if the string matches a regular expression
    //    and move past it if so, returning the match
    //
    Match: function (regex) {
      var match = regex.exec(this.string.substr(this.i));
      if (match) {match = match[0]; this.i += match.length}
      return match;
    },
    
    //
    //  Find a particular character, skipping over braced groups
    //
    Find: function (c) {
      var m = this.string.length, i = this.i, braces = 0;
      while (this.i < m) {
        var C = this.string.charAt(this.i++);
        if (C === c && braces === 0) {return this.string.substr(i,this.i-i-1)}
        if (C === "{") {braces++} else
        if (C === "}") {
          if (braces) {braces--}
          else {
            TEX.Error(["ExtraCloseMissingOpen","Extra close brace or missing open brace"])
          }
        }
      }
      if (braces) {TEX.Error(["MissingCloseBrace","Missing close brace"])}
      TEX.Error(["NoClosingChar","Can't find closing %1",c]);
    }
    
  });
  
  MathJax.Extension["TeX/mhchem"].CE = CE;
  
  /***************************************************************************/
  
  TEX.Definitions.Add({
    macros: {
      //
      //  Set up the macros for chemistry
      //
      ce:   'CE',
      cf:   'CE',
      cee:  'CE',
      
      //
      //  Make these load AMSmath package (redefined below when loaded)
      //
      xleftrightarrow:    ['Extension','AMSmath'],
      xrightleftharpoons: ['Extension','AMSmath'],
      xRightleftharpoons: ['Extension','AMSmath'],
      xLeftrightharpoons: ['Extension','AMSmath'],

      //  FIXME:  These don't work well in FF NativeMML mode
      longrightleftharpoons: ["Macro","\\stackrel{\\textstyle{{-}\\!\\!{\\rightharpoonup}}}{\\smash{{\\leftharpoondown}\\!\\!{-}}}"],
      longRightleftharpoons: ["Macro","\\stackrel{\\textstyle{-}\\!\\!{\\rightharpoonup}}{\\small\\smash\\leftharpoondown}"],
      longLeftrightharpoons: ["Macro","\\stackrel{\\rightharpoonup}{{{\\leftharpoondown}\\!\\!\\textstyle{-}}}"],

      //
      //  Add \hyphen used in some mhchem examples
      //  
      hyphen: ["Macro","\\text{-}"],
      
      //
      //  Handle prescripts and none
      //
      CEprescripts: "CEprescripts",
      CEnone: "CEnone",

      //
      //  Needed for \bond for the ~ forms
      //
      tripledash: ["Macro","\\raise3mu{\\tiny\\text{-}\\kern2mu\\text{-}\\kern2mu\\text{-}}"]
    },
    
    //
    //  Needed for \bond for the ~ forms
    //
    environment: {
      CEstack:       ['Array',null,null,null,'r',null,"0.001em",'T',1]
    }
  },null,true);
  
  if (!MathJax.Extension["TeX/AMSmath"]) {
    TEX.Definitions.Add({
      macros: {
        xrightarrow: ['Extension','AMSmath'],
        xleftarrow:  ['Extension','AMSmath']
      }
    },null,true);
  }
  
  //
  //  These arrows need to wait until AMSmath is loaded
  //
  MathJax.Hub.Register.StartupHook("TeX AMSmath Ready",function () {
    TEX.Definitions.Add({
      macros: {
        //
        //  Some of these are hacks for now
        //
        xleftrightarrow:    ['xArrow',0x2194,6,6],
        xrightleftharpoons: ['xArrow',0x21CC,5,7],  // FIXME:  doesn't stretch in HTML-CSS output
        xRightleftharpoons: ['xArrow',0x21CC,5,7],  // FIXME:  how should this be handled?
        xLeftrightharpoons: ['xArrow',0x21CC,5,7]
      }
    },null,true);
  });

  TEX.Parse.Augment({

    //
    //  Implements \ce and friends
    //
    CE: function (name) {
      var arg = this.GetArgument(name);
      var tex = CE(arg).Parse();
      this.string = tex + this.string.substr(this.i); this.i = 0;
    },
    
    //
    //  Implements \CEprescripts{presub}{presup}{base}{sub}{sup}
    //
    CEprescripts: function (name) {
      var presub = this.ParseArg(name),
          presup = this.ParseArg(name),
          base = this.ParseArg(name),
          sub = this.ParseArg(name),
          sup = this.ParseArg(name);
      var MML = MathJax.ElementJax.mml;
      this.Push(MML.mmultiscripts(base,sub,sup,MML.mprescripts(),presub,presup));
    },
    CEnone: function (name) {
      this.Push(MathJax.ElementJax.mml.none());
    }
    
  });
  
  //
  //  Indicate that the extension is ready
  //
  MathJax.Hub.Startup.signal.Post("TeX mhchem Ready");

});

MathJax.Ajax.loadComplete("[MathJax]/extensions/TeX/mhchem.js");
