/*
 * QRコード格納可能情報量の判別
 *
 */

/*
 * 各種モードのベースクラス
 */
var Mode = Class.create({
    initialize: function(str, version) {
        this.str = str;
        this.version = version;
    },

    str_size_indicator: function() {
        if(this.version > 0 && this.version <= 9) {
            return this.str_size_indicator_array()[0];
        }
        else if(this.version > 9 && this.version <= 26) {
            return this.str_size_indicator_array()[1];
        }
        else if(this.version > 26 && this.version <= 40) {
            return this.str_size_indicator_array()[2];
        }
        else {
            throw "Error: undefined version '"+this.version+"'.";
        }
    },

    str_size_indicator_array: function() {
        return null;
    }
});

var BitByteMode = Class.create(Mode, {
    get_bit_size: function() {
        return 4 + this.str_size_indicator() + 8*this.str.length;
    },

    str_size_indicator_array: function() {
        return [8,16,16];
    }
});

var NumericMode = Class.create(Mode, {
    get_bit_size: function() {
        r = [0,4,7];
        return 4 + this.str_size_indicator() + 10*(this.str.length/3).floor() + r[this.str.length%3];
    },

    str_size_indicator_array: function() {
        return [10,12,14];
    }

});

var AlphaNumericMode = Class.create(Mode, {
    get_bit_size: function() {
        return 4 + this.str_size_indicator() +
            11*(this.str.length/2).floor() + 6*(this.str.length%2);
    },

    str_size_indicator_array: function() {
        return [9,11,13];
    }
});

var KanjiMode = Class.create(Mode, {
    get_bit_size: function() {
        return 4 + this.str_size_indicator() + 13*this.str.length;
    },

    str_size_indicator_array: function() {
        return [8,10,12];
    }
});


/*
 * QR文字データ等から許容量を算出するクラス
 */
var QRData = Class.create({

    initialize: function(data_str, version, ec) {
        // CONSTRUCTOR

        this.NUMERIC_MODE = 1;
        this.ALPHA_NUMERIC_MODE = 2;
        this.BIT_BYTE_MODE = 3;
        this.KANJI_MODE = 4;

        this.current_mode = null;
        this.index_starting_current_mode = 0;
        this.numeric_counter = 0;
        this.alpha_numeric_counter = 0;
        this.kanji_counter = 0;
        this.bit_byte_counter = 0;
        this.segments = [];

        this.capacity_definition =
            new Array(new Array(19,16,13,9),         // version1
                      new Array(34,28,22,16),
                      new Array(55,44,34,26),
                      new Array(80,64,48,36),
                      new Array(108,86,62,46),
                      new Array(136,108,76,60),
                      new Array(156,124,88,66),
                      new Array(194,154,110,86),
                      new Array(232,182,132,100),
                      new Array(274,216,154,122),
                      new Array(324,254,180,140),    // version11
                      new Array(370,290,206,158),
                      new Array(428,334,244,180),
                      new Array(461,365,261,197),
                      new Array(523,415,295,223),
                      new Array(589,453,325,253),
                      new Array(647,507,367,283),
                      new Array(721,563,397,313),
                      new Array(795,627,445,341),
                      new Array(861,669,485,385),
                      new Array(932,714,512,406),    // version21
                      new Array(1006,782,568,442),
                      new Array(1094,860,614,464),
                      new Array(1174,914,664,514),
                      new Array(1276,1000,718,538),
                      new Array(1370,1062,754,596),
                      new Array(1468,1128,808,628),
                      new Array(1531,1193,871,661),
                      new Array(1631,1267,911,701),
                      new Array(1735,1373,985,745),
                      new Array(1843,1455,1033,793), // version31
                      new Array(1955,1541,1115,845),
                      new Array(2071,1631,1171,901),
                      new Array(2191,1725,1231,961),
                      new Array(2306,1812,1286,986),
                      new Array(2434,1914,1354,1054),
                      new Array(2566,1992,1426,1096),
                      new Array(2702,2102,1502,1142),
                      new Array(2812,2216,1582,1222),
                      new Array(2956,2334,1666,1276));

        this.data_str = data_str;
        this.ec = ec;
        // バージョンが設定されていない場合はデフォルトで8としておく
        if(version == 0) {
            this.version = 10;
        }
        else {
            this.version = version;
        }


        // ------------------
        // PRIVATE METHOD
        //
        this.calc_optimized_bit_size =
            function() {
                this.define_init_mode();

                // 以降の処理
                for(i = 0; i < this.data_str.length; i++) {
                    if(this.current_mode == this.BIT_BYTE_MODE) {
                        target_modes_to_count =
                            new Hash({ numeric: [6,8,9],
                                       alpha_numeric: [11,15,16],
                                       kanji: [1,1,1]});
                        this.algorithm_frame(i, this.BIT_BYTE_MODE, target_modes_to_count);
                    }
                    else if(this.current_mode == this.ALPHA_NUMERIC_MODE) {
                        target_modes_to_count =
                            new Hash({ numeric: [13,15,17],
                                       bit_byte: [1,1,1],
                                       kanji: [1,1,1] });
                        this.algorithm_frame(i, this.ALPHA_NUMERIC_MODE, target_modes_to_count);
                    }
                    else if(this.current_mode == this.NUMERIC_MODE) {
                        target_modes_to_count =
                            new Hash({ bit_byte: [1,1,1],
                                       alpha_numeric: [1,1,1],
                                       kanji: [1,1,1] });
                        this.algorithm_frame(i, this.NUMERIC_MODE, target_modes_to_count);
                    }
                    else if(this.current_mode == this.KANJI_MODE) {
                        break;
                    }
                }
                // 最後のセグメントを追加
                this.segments.push(this.newCurrentMode(this.data_str.slice(this.index_starting_current_mode), this.version));

                var total_bit_size = 0;
                this.segments.each(function(segment) {
                    var size = segment.get_bit_size();
                    total_bit_size += size;
                });

                return total_bit_size;
            }

        /*
         * どの文字種とするかの識別
         * return 1: 数字, 2: 英数, 3: 8bit, 4: 漢字
         */
        this.what_set =
            function(single_char) {

                // 文字種判別
                if(single_char.charCodeAt(0) <= 0xFF &&
                   single_char.charCodeAt(0) != 0x20 &&
                   single_char.charCodeAt(0) != 0x24 &&
                   single_char.charCodeAt(0) != 0x25 &&
                   single_char.charCodeAt(0) != 0x2A &&
                   single_char.charCodeAt(0) != 0x2B &&
                   (single_char.charCodeAt(0) < 0x2D ||
                    single_char.charCodeAt(0) > 0x3A) &&
                   (single_char.charCodeAt(0) < 0x41 ||
                    single_char.charCodeAt(0) > 0x5A)) {
                    // 8bitバイトモード
                    return this.BIT_BYTE_MODE;
                }
                else if((single_char.charCodeAt(0) >= 0x41 &&
                         single_char.charCodeAt(0) <= 0x5A) ||
                        single_char.charCodeAt(0) == 0x20 ||
                        single_char.charCodeAt(0) == 0x24 ||
                        single_char.charCodeAt(0) == 0x25 ||
                        single_char.charCodeAt(0) == 0x2A ||
                        single_char.charCodeAt(0) == 0x2B ||
                        single_char.charCodeAt(0) == 0x2D ||
                        single_char.charCodeAt(0) == 0x2E ||
                        single_char.charCodeAt(0) == 0x2F ||
                        single_char.charCodeAt(0) == 0x3A) {
                    // 英数
                    return this.ALPHA_NUMERIC_MODE;
                }
                else if(single_char.match(/[0-9]/)) {
                    // 数字
                    return this.NUMERIC_MODE;
                }
                else {
                    // 漢字
                    return this.KANJI_MODE;
                }
            }

        this.index_by_version =
            function() {
                if(this.version > 0 && this.version <= 9) { return 0;}
                else if(this.version > 9 && this.version <= 27) { return 1;}
                else if(this.version > 27 && this.version <= 40) { return 2;}
                else { throw "Error: undefined version '"+this.version+"'."; }
            }

        this.define_init_mode =
            function() {
                current_char = this.data_str.charAt(0);

                // 初期モード判別
                if(this.what_set(current_char) == this.KANJI_MODE) {
                    // 漢字モード
                    this.current_mode = this.KANJI_MODE;
                }
                else if(this.what_set(current_char) == this.BIT_BYTE_MODE) {
                    // 8bitモード
                    this.current_mode = this.BIT_BYTE_MODE;
                }
                else if(this.what_set(current_char) == this.ALPHA_NUMERIC_MODE) {
                    for(i = 1; i <= ([6,7,8])[this.index_by_version()]; i++) {
                        if(this.what_set(this.data_str.charAt(i)) == this.BIT_BYTE_MODE) {
                            // 8bitモード
                            this.current_mode = this.BIT_BYTE_MODE;
                            break;
                        }
                    }
                    if(this.current_mode == null) {
                        // 英数モード
                        this.current_mode = this.ALPHA_NUMERIC_MODE;
                    }
                }
                else if(current_char.match(/[0-9]/)) {
                    for(i = 1; i <= ([7,8,9])[this.index_by_version()]; i++) {
                        if(i <= ([4,4,5])[this.index_by_version()] &&
                           this.what_set(this.data_str.charAt(i)) == this.BIT_BYTE_MODE) {
                            // 8bitモード
                            this.current_mode = this.BIT_BYTE_MODE;
                            break;
                        }
                        else if(this.what_set(this.data_str.charAt(i)) ==
                                this.ALPHA_NUMERIC_MODE) {
                            // 英数モード
                            this.current_mode = this.ALPHA_NUMERIC_MODE;
                            break;
                        }
                    }
                    if(this.current_mode == null) {
                        // 数字モード
                        this.current_mode = this.NUMERIC_MODE;
                    }
                }
            }

        this.algorithm_frame =
            function(index, current_mode, target_modes_to_count) {
                // 連続する文字種のカウント
                // その他はリセット
                switch(this.what_set(this.data_str.charAt(index))) {
                case this.NUMERIC_MODE:
                    this.numeric_counter++;
                    this.alpha_numeric_counter = 0;
                    this.bit_byte_counter = 0;
                    this.kanji_counter = 0;
                    break;
                case this.ALPHA_NUMERIC_MODE:
                    this.numeric_counter = 0;
                    this.alpha_numeric_counter++;
                    this.bit_byte_counter = 0;
                    this.kanji_counter = 0;
                    break;
                case this.BIT_BYTE_MODE:
                    this.numeric_counter = 0;
                    this.alpha_numeric_counter = 0;
                    this.bit_byte_counter++;
                    this.kanji_counter = 0;
                    break;
                case this.KANJI_MODE:
                    this.numeric_counter = 0;
                    this.alpha_numeric_counter = 0;
                    this.bit_byte_counter = 0;
                    this.kanji_counter++;
                    break;
                }

                // 条件(件数)に合えば新しいセグメント開始のため、
                // 以前のセグメントを保存して区切る
                // 区切りパターン: 指定件数を越えた場合
                if(target_modes_to_count.get('numeric') != null &&
                   this.numeric_counter >=
                   (target_modes_to_count.get('numeric'))[this.index_by_version()]) {
                    this.segments.push(this.newCurrentMode(this.data_str.slice(this.index_starting_current_mode, i - this.numeric_counter + 1)));
                    this.index_starting_current_mode = i - this.numeric_counter + 1;
                    this.current_mode = this.NUMERIC_MODE;
                }
                else if(target_modes_to_count.get('alpha_numeric') != null &&
                        this.alpha_numeric_counter >=
                        (target_modes_to_count.get('alpha_numeric'))[this.index_by_version()]) {
                    this.segments.push(this.newCurrentMode(this.data_str.slice(this.index_starting_current_mode, i - this.alpha_numeric_counter + 1)));
                    this.index_starting_current_mode = i - this.alpha_numeric_counter + 1;
                    this.current_mode = this.ALPHA_NUMERIC_MODE;
                }
                else if(target_modes_to_count.get('bit_byte') != null &&
                        this.bit_byte_counter >=
                        (target_modes_to_count.get('bit_byte'))[this.index_by_version()]) {
                    this.segments.push(this.newCurrentMode(this.data_str.slice(this.index_starting_current_mode, i - this.bit_byte_counter + 1)));
                    this.index_starting_current_mode = i - this.bit_byte_counter + 1;
                    this.current_mode = this.BIT_BYTE_MODE;
                }
                else if(target_modes_to_count.get('kanji') != null &&
                        this.kanji_counter >=
                        (target_modes_to_count.get('kanji'))[this.index_by_version()]) {
                    this.segments.push(this.newCurrentMode(this.data_str.slice(this.index_starting_current_mode, i - this.kanji_counter + 1)));
                    this.index_starting_current_mode = i - this.kanji_counter + 1;
                    this.current_mode = this.KANJI_MODE;
                }
            }

        this.newCurrentMode =
            function(str) {
                switch(this.current_mode) {
                case this.NUMERIC_MODE:
                    return new NumericMode(str, this.version);
                case this.ALPHA_NUMERIC_MODE:
                    return new AlphaNumericMode(str, this.version);
                case this.BIT_BYTE_MODE:
                    return new BitByteMode(str, this.version);
                case this.KANJI_MODE:
                    return new KanjiMode(str, this.version);
                }
            }

    },


    // -------------
    // PUBLIC METHOD
    //

    /*
     * パーセンテージを計算する
     */
    calc_percentage: function () {
        if(this.version > 0) {
            capacity =
                this.capacity_definition[this.version - 1][["L","M","Q","H"].indexOf(this.ec)];
        }
        else {
            capacity = this.capacity_definition[9][["L","M","Q","H"].indexOf(this.ec)];
        }

        optimized_bit_size = this.data_str.length == 0 ?
            0 : this.calc_optimized_bit_size();
        return (optimized_bit_size*100 / (capacity*8)).ceil();
    }

});


function calc_number_of_module(version) {
    return 17 + 4*version;
}
