/**
 * Language module
 * @module lang
 */

/**
 * Language spesific functions such as charsets and number pronounciation
 * @hideconstructor
 */
class Lang {
  //These static members must be overriden in extended classes;
  //otherwise, the class won't function properly
  static CS = {};
  static TR = {};
  static langCode = "";

  //==========================================
  // STATIC FUNCTIONS (protected)
  //==========================================

  /**
  * Add char sets of a language
  *
  * @protected
  * @static
  * @param  {String} setName CharSet name, for example: hiragana, kanji, Arabic suppliment
  * @param  {Number} begin   integer value: begining of the charSet
  * @param  {Number} end     integer value: end of the charSet
  */
  static _nChs(setName, begin, end) {
    this.CS[setName] = __isBetween(begin, end);
  }

  /**
  * Creates a new transformation method
  *
  * @protected
  * @static
  * @param {String} transName   transformation name (function name), for example: hiragana2Katakana
  * @param {Object[]} opts   Array of options
  * @example
  *    {//If the charset exists and we use all of it
  *       setName: "<name of the charset",
  *       offset: <number>
  *    },
  *    {//otherwise
  *       begin: <number>,
  *       end: <number>,
  *       offset: <number>
  *    }
  */
  static _nTrans(transName, transDesc, opts) {
    let transOpts = [];
    for(let i=0; i<opts.length; i++) {
      let charSetFunc = function() { return false; };
      let opt = opts[i];
      //helpfull for code minification
      let setName = opt.setName,
      begin = opt.begin,
      end = opt.end;
      if (setName && setName in this.CS) {
        charSetFunc = this.CS[setName];
      }
      else if (typeof begin === "number" && typeof end === "number") {
        charSetFunc = __isBetween(begin, end);
      }

      transOpts.push({
        offset: opt.offset,
        found: charSetFunc
      });
    }

    this.TR[transName] = {
      fct:  __createTransform(transOpts),
      desc: transDesc
    };
  }

  //==========================================
  // CHARSETS FUNCTIONS (public)
  //==========================================

  /**
  * Returns the available charsets for the current language
  *
  * @public
  * @static
  * @final
  * @return {String[]} a set of strings containing the names of charsets
  */
  static lchars() {
    return Object.keys(this.CS);
  }

  /**
  * Returns the available charsets for the current language
  *
  * @public
  * @static
  * @final
  * @return {String[]} a set of strings containing the names of charsets
  */
  static listCharSets(){
    return this.lchars();
  }

  /**
  * Get an object with verification functions
  * contains: returns a function which verifies if the text contains
  *           at least one of the charsets
  * all : returns a function which verifies if all the chacaters of the text
  *           are in this charset
  *
  * @public
  * @static
  * @final
  * @param  {String} charSet the name of the charset
  * @return {object} object with verifying functions
  */
  static gcharverify(charSet) {
    let chVerif = this.CS[charSet];
    if (chVerif) {
      return {
        contains: __createContains(chVerif),
        all: __createAll(chVerif)
      };
    }
    return charFctsDef;
  }

  /**
  * Get an object with verification functions
  * contains: returns a function which verifies if the text contains
  *           at least one of the charsets
  * all : returns a function which verifies if all the chacaters of the text
  *           are in this charset
  *
  * @public
  * @static
  * @final
  * @param  {String} charSet the name of the charset
  * @return {object} object with verifying functions
  */
  static getCharSetFunctions(charSet) {
    return this.gcharverify(charSet);
  }

  /**
  * Sets the current charset we are using for functions
  *
  * @public
  * @final
  * @param  {String} charSet the name of the charset
  */
  static schars(charSet) {
    this.charFcts = this.gcharverify(charSet);
  }

  /**
   * Verify if all text's characters belong to the current charSet
   * @public
   * @final
   * @param  {String} text the text to verify
   * @return {Boolean}      the text contains some chars of the current charset or not
   */
  static contains(text){
    return this.charFcts.contains(text);
  }

  /**
   * Verify if all text's characters belong to the current charSet
   * @public
   * @final
   * @param  {String} text the text to verify
   * @return {Boolean}      the text contains some chars of the current charset or not
   */
  static all(text){
    return this.charFcts.all(text);
  }



  //==========================================
  // TRANSFORMATION FUNCTIONS (public)
  //==========================================

  /**
  * Returns the available transformations for the current language
  * (Static version)
  * @public
  * @static
  * @final
  * @return {String[]} a set of strings containing the names of transformation functions
  */
  static ltrans() {
    return Object.keys(this.TR);
  }

  /**
  * Returns the available transformations for the current language
  * (Static version)
  * @public
  * @static
  * @final
  * @return {String[]} a set of strings containing the names of transformation functions
  */
  static listTransformations(){
    return this.ltrans();
  }

  /**
  * Get the the transformation function
  *
  * @public
  * @static
  * @final
  * @param  {String} transName name of the transformation function
  * @return {function}  a function which transforms a given text
  */
  static gtrans(transName) {
    return (transName in this.TR)? this.TR[transName].fct: function(text){return text};
  }

  /**
  * Get the the transformation function
  *
  * @public
  * @static
  * @final
  * @param  {String} transName name of the transformation function
  * @return {function}  a function which transforms a given text
  */
  static getTransformationFunction(transName) {
    return this.gtrans(transName);
  }

  /**
  * Get the description of the transformation function
  *
  * @public
  * @static
  * @final
  * @param  {String} transName name of the transformation function
  * @return {Text}  description
  */
  static gtransdesc(transName) {
    if (transName in this.TR){
      return this.TR[transName].desc;
    }
    return "";
  }

  /**
  * Get the description of the transformation function
  *
  * @public
  * @static
  * @final
  * @param  {String} transName name of the transformation function
  * @return {Text}  description
  */
  static getTransDesc(transName){
    return this.gtransdesc(transName);
  }


  /**
  * Sets the current transformations for the current object
  *
  * @public
  * @final
  * @param  {String} transName the name of transformation method
  */
  static strans(transName) {
    this.transFct = this.gtrans(transName);
  }

  /**
  * Transforms a text from a charset to another
  *
  * @public
  * @final
  * @param  {String} text text to be transformed
  * @return {Text}  transformed text
  */
  static trans(text) {
    return this.transFct(text);
  }


  //==========================================
  // PRONOUNCE FUNCTIONS
  //==========================================

  /**
  * A function which returns the pronounciation of a number in the destination
  * language (this must be overriden)
  *
  * @public
  * @static
  * @abstract
  * @param  {Number} num A number to be transformed into letters
  * @return {String}  the pronounciation
  */
  static nbr2str(num) {
    return num;
  }

  static pronounceNumber(num) {
    return this.nbr2str(num);
  }


} //End of class Lang

let charFctsDef = {
  contains: (_text) => {return false},
  all: (_text) => {return false}
}

/**
* Returns unicode
*
* @private
* @static
* @memberof Lang
* @param {(Char|Number)} c - a character or a unicode int
* @return {Number} returns the same number or the unicode of the first character
*/
function __getUnicode(char) {
  let type = typeof char;
  if (type === "number")
  return char;

  if (type === "string" && char != "")
  return char.charCodeAt(0);

  return -1;
}

/**
* A function which verifies if a character belongs to a charset
* @callback isInCharset
* @param {(Char|Number)} c the character to be verified (a character or its unicode)
* @return {Boolean} belongs true if it belongs, false otherwise
*/

/**
* Returns another method to verify if a character's unicode is between
* two unicode numbers min and max
* @private
* @static
* @memberof Lang
* @param  {Number}  min minimum unicode (included)
* @param  {Number}  max maximum unicode (included)
* @return {isInCharset}     function with char as parameter and returns a boolean
*/
function __isBetween(min,  max) {
  return function (char) {
    let u = __getUnicode(char);
    if(min <= u && u <= max) return true;
    return false;
  };
}

/**
* Returns a function testing if a text contains at least one character of a given charset
* @private
* @static
* @memberof Lang
* @param  {isInCharset} charTestFunc A function which tests if a character belongs to a charset
* @return {Function}      function which tests if a text contains any charcater of the charset verified by charTestFunc
*/
function __createContains(charTestFunc) {
  return function(text) {
    for (let i = 0; i < text.length; i++) {
      let u = text.charCodeAt(i);
      if (charTestFunc(u)) return true;
    }
    return false;
  };
}

/**
* Returns a function testing if a text's characters are all of a given charset
* @private
* @static
* @memberof Lang
* @param  {isInCharset} charTestFunc A function which tests if a character belongs to a charset
* @return {Function}      function which tests if a text is formed completly by charcaters of the charset verified by charTestFunc
*/
function __createAll(charTestFunc) {
  return function(text) {
    for (let i = 0; i < text.length; i++) {
      let u = text.charCodeAt(i);
      if (! charTestFunc(u)) return false;
    }
    return true;
  };
}

/**
* transformation Function, returns another function that transforms a text
*
* @private
* @static
* @memberof Lang
* @param  {Object[]}  opts a list of objects, where each object is:
*     {
*        offset: the offset of transformation
*        found: the function that verifies if the char can be transformed
*     }
* @return {Function}     function witch transforms a text using the afforded offsets
*/
function __createTransform (opts) {
  return function(text) {
    let result = "";
    for (let i = 0; i < text.length; i++) {
      let u = text.charCodeAt(i);
      for (let j=0;j<opts.length; j++) {
        let opt = opts[j];
        //console.log(opt);
        if (opt.found(u)) {
          u += opt.offset;
          break;
        }
      }
      result += String.fromCharCode(u);
    }
    return result;
  };
}

export default Lang;