/* -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */

'use strict';

(function() {

  var keyboard, ime, composingText, JangmiIME;

  var SPACE = KeyEvent.DOM_VK_SPACE;
  var BACKSPACE = KeyEvent.DOM_VK_BACK_SPACE;
  var RETURN = KeyEvent.DOM_VK_RETURN;

  /*
  JangmiIME
  https://github.com/sangpire/jangmiIME

  Copyright 2013, BYUN Sangpil. All Rights Reserved.

  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.
  */
  (function() {
    var isCompatibleJamoKeyCode;

    var UTF8_HANGUL_SYLLABLES_START = 0xAC00;
    var UTF8_HANGUL_COMPATIBILITY_START = 0x3131;
    var UTF8_JUNGSEONG_START = 0x1161;
    var UTF8_CHOSEONG_DIFF = '까'.charCodeAt(0) - '가'.charCodeAt(0);
    var UTF8_JUNGSEONG_DIFF = '개'.charCodeAt(0) - '가'.charCodeAt(0);

    var COMPATIBILITY_MULTI_JAMO_MAP = {
      'ㄱㄱ' : 0x1, 'ㄷㄷ' : 0x7, 'ㅂㅂ' : 0x12, 'ㅅㅅ' : 0x15,
      'ㅈㅈ' : 0x18, 'ㅗㅏ' : 0x27, 'ㅗㅐ' : 0x28, 'ㅗㅣ' : 0x29,
      'ㅜㅓ' : 0x2C, 'ㅜㅔ' : 0x2E, 'ㅜㅣ' : 0x2E, 'ㅡㅣ' : 0x31
    };

    var CHO_SEONG_MAP = {
      'ㄱ' : 0, 'ㄱㄱ' : 1, 'ㄲ' : 1, 'ㄴ' : 2, 'ㄷ' : 3,
      'ㄷㄷ' : 4, 'ㄸ' : 4, 'ㄹ' : 5, 'ㅁ' : 6, 'ㅂ' : 7,
      'ㅂㅂ' : 8, 'ㅃ' : 8, 'ㅅ' : 9, 'ㅅㅅ' : 10, 'ㅆ' : 10,
      'ㅇ' : 11, 'ㅈ' : 12, 'ㅈㅈ' : 13, 'ㅉ' : 13, 'ㅊ' : 14,
      'ㅋ' : 15, 'ㅌ' : 16, 'ㅍ' : 17, 'ㅎ' : 18
    };

    var JUNG_SEONG_MAP = {
      'ㅏ' : 0, 'ㅐ' : 1, 'ㅑ' : 2, 'ㅒ' : 3, 'ㅓ' : 4,
      'ㅔ' : 5, 'ㅕ' : 6, 'ㅖ' : 7, 'ㅗ' : 8, 'ㅘ' : 9,
      'ㅗㅏ' : 9, 'ㅙ' : 10, 'ㅗㅐ' : 10, 'ㅚ' : 11, 'ㅗㅣ' : 11,
      'ㅛ' : 12, 'ㅜ' : 13, 'ㅝ' : 14, 'ㅜㅓ' : 14, 'ㅞ' : 15,
      'ㅜㅔ' : 15, 'ㅟ' : 16, 'ㅜㅣ' : 16, 'ㅠ' : 17, 'ㅡ' : 18,
      'ㅢ' : 19, 'ㅡㅣ' : 19, 'ㅣ' : 20
    };

    var JONG_SEONG_MAP = {
      'ㄱ' : 1, 'ㄲ' : 2, 'ㄱㅅ' : 3, 'ㄴ' : 4, 'ㄴㅈ' : 5,
      'ㄴㅎ' : 6, 'ㄷ' : 7, 'ㄹ' : 8, 'ㄹㄱ' : 9, 'ㄹㅁ' : 10,
      'ㄹㅂ' : 11, 'ㄹㅅ' : 12, 'ㄹㅌ' : 13, 'ㄹㅍ' : 14, 'ㄹㅎ' : 15,
      'ㅁ' : 16, 'ㅂ' : 17, 'ㅂㅅ' : 18, 'ㅅ' : 19, 'ㅆ' : 20,
      'ㅇ' : 21, 'ㅈ' : 22, 'ㅊ' : 23, 'ㅋ' : 24, 'ㅌ' : 25,
      'ㅍ' : 26, 'ㅎ' : 27
    };

    isCompatibleJamoKeyCode = function(keyCode) {
      return (0x3131 <= keyCode && keyCode <= 0x318e);
    };

    JangmiIME = (function() {
      /*
      @handler =
      passed: (keyCode) ->
      added: (gul) ->
      changed: (gul) ->
      */

      function JangmiIME(handler) {
        this.handler = handler;
        this.reset();
      }

      /*
      Reset Current State.
      */
      JangmiIME.prototype.reset = function() {
        this.curJamo = null;
        this.choSeong = [];
        this.jungSeong = [];
        this.jongSeong = [];
      };

      JangmiIME.prototype.added = function() {
        var curGul;
        curGul = this.peep();
        if (curGul != null) {
          return this.handler.added(curGul);
        }
      };

      JangmiIME.prototype.changed = function() {
        var curGul;
        curGul = this.peep();
        if (curGul != null) {
          return this.handler.changed(curGul);
        }
      };

      JangmiIME.prototype.isEmpty = function() {
        return (this.choSeong.length +
          this.jungSeong.length +
          this.jongSeong.length) === 0;
        };

        /*
        Add KeyCode
        */
        JangmiIME.prototype.add = function(keyCode) {
          var curJamo, jamo, temp;
          if (!isCompatibleJamoKeyCode(keyCode)) {
            this.reset();
            return this.handler.passed(keyCode);
          } else {
            jamo = String.fromCharCode(keyCode);
            if (this.curJamo == null) {
              if (CHO_SEONG_MAP.hasOwnProperty(jamo)) {
                this.curJamo = this.choSeong;
              }
              if (JUNG_SEONG_MAP.hasOwnProperty(jamo)) {
                this.curJamo = this.jungSeong;
              }
              this.curJamo.push(jamo);
              return this.added();
            } else {
              curJamo = this.curJamo.join('') + jamo;
              switch (this.curJamo) {
                case this.choSeong:
                  if (CHO_SEONG_MAP.hasOwnProperty(curJamo)) {
                    this.curJamo.push(jamo);
                    return this.changed();
                  } else {
                    if (JUNG_SEONG_MAP.hasOwnProperty(jamo)) {
                      this.jungSeong.push(jamo);
                      this.curJamo = this.jungSeong;
                      return this.changed();
                    } else {
                      this.reset();
                      return this.add(keyCode);
                    }
                  }
                  break;
                  case this.jungSeong:
                    if (JUNG_SEONG_MAP.hasOwnProperty(curJamo)) {
                      this.curJamo.push(jamo);
                      return this.changed();
                    } else {
                      if (this.choSeong.length > 0 &&
                        JONG_SEONG_MAP.hasOwnProperty(jamo)) {
                          this.jongSeong.push(jamo);
                          this.curJamo = this.jongSeong;
                          return this.changed();
                        } else {
                          this.reset();
                          return this.add(keyCode);
                        }
                      }
                      break;
                      case this.jongSeong:
                        if (JONG_SEONG_MAP.hasOwnProperty(curJamo)) {
                          this.curJamo.push(jamo);
                          return this.changed();
                        } else {
                          if (JUNG_SEONG_MAP.hasOwnProperty(jamo)) {
                            temp = this.curJamo.pop();
                            this.changed();
                            this.reset();
                            this.choSeong.push(temp);
                            this.jungSeong.push(jamo);
                            this.curJamo = this.jungSeong;
                            return this.added();
                          } else {
                            this.reset();
                            this.curJamo = null;
                            return this.add(keyCode);
                          }
                        }
                      }
                    }
                  }
                };

                /*
                Delete Last Input
                */
                JangmiIME.prototype.back = function() {
                  if (this.curJamo.length > 0) {
                    this.curJamo.pop();
                    return this.changed();
                  } else {
                    switch (this.curJamo) {
                      case this.jungSeong:
                        this.curJamo = this.choSeong;
                        return this.back();
                        case this.jongSeong:
                          this.curJamo = this.jungSeong;
                          return this.back();
                        }
                      }
                    };

                    /*
                    peep current hangul
                    */
                    JangmiIME.prototype.peep = function() {
                      var code;
                      if (this.choSeong.length > 0 && this.jungSeong.length > 0) {
                        code = UTF8_HANGUL_SYLLABLES_START +
                        CHO_SEONG_MAP[this.choSeong.join('')] * UTF8_CHOSEONG_DIFF;
                        code += JUNG_SEONG_MAP[this.jungSeong.join('')] * UTF8_JUNGSEONG_DIFF;
                        if (this.jongSeong.length > 0) {
                          code += JONG_SEONG_MAP[this.jongSeong.join('')];
                        }
                      } else {
                        if (this.choSeong.length === 1) {
                          code = this.choSeong[0].charCodeAt(0);
                        } else if (this.choSeong.length > 1) {
                          code = UTF8_HANGUL_COMPATIBILITY_START +
                          COMPATIBILITY_MULTI_JAMO_MAP[this.choSeong.join('')];
                        } else if (this.jungSeong.length === 1) {
                          code = this.jungSeong[0].charCodeAt(0);
                        } else if (this.jungSeong.length > 1) {
                          code = UTF8_HANGUL_COMPATIBILITY_START +
                          COMPATIBILITY_MULTI_JAMO_MAP[this.jungSeong.join('')];
                        } else {
                          return '';
                        }
                      }
                      return String.fromCharCode(code);
                    };
                    return JangmiIME;
                  })();
                })();

                InputMethods.jshangul = {
                  init: init,
                  click: click,
                  deactivate: deactivate
                };

                function setComposition(gul) {
                  composingText = gul;
                  keyboard.setComposition(gul);
                }

                function endComposition() {
                  if (composingText.length > 0) {
                    keyboard.endComposition(composingText);
                    composingText = '';
                  }
                }

                function init(interfaceObject) {
                  keyboard = interfaceObject;
                  composingText = '';
                  ime = new JangmiIME({
                    passed: function(keyCode) {
                      endComposition();
                      keyboard.sendKey(keyCode);
                    },
                    added: function(gul) {
                      endComposition();
                      setComposition(gul);
                    },
                    changed: function(gul) {
                      setComposition(gul);
                    }
                  });
                }

                function deactivate() {
                  endComposition();
                  ime.reset();
                }

                function click(keycode, x, y) {
                  if (keycode === BACKSPACE && !ime.isEmpty()) {
                    ime.back();
                  } else {
                    ime.add(keycode);
                  }
                  keyboard.resetUpperCase();
                }
              })();
