/* -*- coding: utf-8 -*-
 *
 * Copyright (C) 2010 by Atzm WATANABE <atzm@atzm.org>
 *
 *  This program is free software; you can redistribute it and/or modify it
 *  under the terms of the GNU General Public License (version 2) as
 *  published by the Free Software Foundation.  It is distributed in the
 *  hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
 *  implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 *  PURPOSE.  See the GNU General Public License for more details.
 *
 * $Id$
 *
 */

System = {
    "screen": {
        "canvas": null,
        "ctx":    null,
        "width":  0,
        "height": 0
    },
    "sound":            {},
    "message":          null,
    "enemyImages":      new Array(),
    "enemies":          new Array(),
    "players":          new Array(),
    "score":            {},
    "backgroundObject": new Array(),
    "deathPieces":      new Array(),
    "mainIntervalId":   0
};


/*
 *  Tiun Tiun Utilities
 */
var DeathPiece = function(sizes, colors, x, y, dir, speed) {
    var that = new LinerBullet(sizes[0], colors[0], null,
                               System.screen.width, System.screen.height,
                               x, y, dir, speed);

    var sizeIdx  = -1;
    var colorIdx = -1;

    that.getSize = function() {
        if (++sizeIdx >= sizes.length)
            sizeIdx = 0;
        return sizes[sizeIdx];
    };

    that.getColor = function() {
        if (++colorIdx >= colors.length)
            colorIdx = 0;
        return colors[colorIdx];
    };

    return that;
};

function addDeathPieces(x, y, sizes, colors, speed, way) {
    if (way % 2)
        way++;

    var pieces = new Array();
    var angle  = 0;
    var delta  = 2 / way;

    for(var i = 0; i < way; i++) {
        pieces.push(new DeathPiece(sizes, colors, x, y, angle * Math.PI, speed));
        angle += delta;
    }

    System.deathPieces.push(pieces);
}

function updateDeathPieces(ctx, width, height) {
    var newObjs = new Array();

    for (var i = 0; i < System.deathPieces.length; i++) {
        var pieces    = System.deathPieces[i];
        var newPieces = new Array();

        for (var k = 0; k < pieces.length; k++) {
            pieces[k].next();
            pieces[k].draw(ctx);
            if (!pieces[k].vanished(width, height))
                newPieces.push(pieces[k]);
        }

        if (newPieces.length)
            newObjs.push(newPieces);
    }

    System.deathPieces = newObjs;
}


/*
 *  Utility Functions
 */
function setMessage(elem, msg) {
    if (elem)
        elem.innerHTML = msg;
}

function addMessage(elem, msg) {
    if (elem)
        elem.innerHTML += msg;
}

function updateBackground(ctx, width, height, size, color, max) {
    if (System.backgroundObject.length < max) {
        var x = Math.ceil(Math.random() * width);
        var s = Math.ceil(Math.random() * 5);
        System.backgroundObject.push(
            new LinerBullet(size, color, null,
                            System.screen.width, System.screen.height,
                            x, 0, 0.5 * Math.PI, s));
    }

    var newObjs = new Array();

    for (var i = 0; i < System.backgroundObject.length; i++) {
        System.backgroundObject[i].next();
        System.backgroundObject[i].draw(ctx);
        if (!System.backgroundObject[i].vanished(width, height))
            newObjs.push(System.backgroundObject[i]);
    }

    System.backgroundObject = newObjs;
}

function drawScreen(ctx, op, style, width, height) {
    var c = ctx.globalCompositeOperation;
    ctx.globalCompositeOperation = op;
    ctx.beginPath();
    ctx.fillStyle = style;
    ctx.fillRect(0, 0, width, height);
    ctx.fill();
    ctx.closePath();
    ctx.globalCompositeOperation = c;
}

function drawLifeGauge(ctx, op, trooper, x, y, width) {
    var length = trooper.life;

    if (length > width - 20)
        length = width - 20;

    var c = ctx.globalCompositeOperation;
    ctx.globalCompositeOperation = op;
    ctx.beginPath();
    ctx.fillStyle = trooper.color;
    ctx.fillRect(x, y, length, 10);
    ctx.fill();
    ctx.closePath();
    ctx.globalCompositeOperation = c;

    drawString(ctx, op, trooper.life, x + 2, y + 8, trooper.color,
               "6pt monospace", "left");
}

function drawString(ctx, op, string, x, y, color, font, align) {
    var a = ctx.textAlign;
    var f = ctx.font;
    var c = ctx.globalCompositeOperation;
    ctx.globalCompositeOperation = op;
    ctx.beginPath();
    ctx.textAlign = align;
    ctx.fillStyle = color;
    ctx.font      = font;
    ctx.fillText(string, x, y);
    ctx.fill();
    ctx.textAlign = a;
    ctx.font      = f;
    ctx.closePath();
    ctx.globalCompositeOperation = c;
}

function updateTrooper(trooper, enemyKey) {
    trooper.update(System[enemyKey]);

    var aliveEnemies = new Array();
    for (var i = 0; i < System[enemyKey].length; i++) {
        var enemy = System[enemyKey][i];

        if (enemy.isDead()) {
            playSound("se_destroy");

            addDeathPieces(
                enemy.x, enemy.y,
                [6, 8, 10], ["#55F", "#AAF"], 3, 8
            );

            if (System.score[trooper.name] !== undefined) {
                System.score[trooper.name] += enemy.maxLife * 100;
            }
        }
        else {
            aliveEnemies.push(enemy);
        }
    }
    System[enemyKey] = aliveEnemies;

    trooper.draw(System.screen.ctx);
}

function switchStageBgm() {
    var sum    = 0;
    var stage  = 1;
    var stages = new Array();
    var base   = 50000;
    var score  = 0;
    var scores = Object.keys(System.score);

    for (var i = 0; i < scores.length; i++) {
        sum += System.score[scores[i]];
    }
    score = Math.round(sum / scores.length);

    for (var name in System.sound) {
        if (!name.match(/^bgm_stage/)) continue;
        stages.push(name);
    }
    stage = Math.floor((score % (stages.length * base)) / base) + 1;

    for (var i = 0; i < stages.length; i++) {
        if (("bgm_stage" + stage) == stages[i]) {
            playSound(stages[i]);
        }
        else {
            pauseSound(stages[i], true);
        }
    }
}

function toggleSound(val) {
    for (var name in System.sound)
        System.sound[name].muted = !val;
}

function registerSound(name, audio) {
    System.sound[name] = audio;
}

function playSound(name) {
    if (System.sound[name])
        System.sound[name].play();
}

function pauseSound(name, stop) {
    if (System.sound[name]) {
        System.sound[name].pause();
        if (stop)
            System.sound[name].currentTime = 0;
    }
}

function numEnemies() {
    return System.enemies.length;
}

function addEnemyImage(image) {
    System.enemyImages.push(image);
}

function addEnemy(enemyData) {
    var actList = EnemyActionLists[enemyData.mtime % EnemyActionLists.length];
    var shot    = EnemyShots[enemyData.hitpoint % EnemyShots.length];
    var numAct  = enemyData.agility       % (EnemyActions.length  - 1) + 1;
    var numBlt  = enemyData.skills.length % (EnemyBullets.length  - 1) + 1;
    var numBrrg = enemyData.skills.length % (EnemyBarrages.length - 1) + 1;
    var acts    = new Array();
    var brrgs   = new Array();

    var bulletWay         = Math.ceil(enemyData.concentration / 10);
    var bulletInterval    = Math.round(50 * 1 / Math.log(enemyData.skillpoint + 0.1));
    var bulletSize        = Math.round(Math.log(enemyData.luck + 1));
    var bulletFrameWidth  = (bulletSize + 5) * 2;
    var bulletFrameHeight = (bulletSize + 5) * 4;
    var bulletSpeed       = enemyData.strength / 15;

    bulletSpeed = Math.log(bulletSpeed < 1.5 ? 1.5 : bulletSpeed);

    for (var i = 0; i < numAct; i++) {
        var idx = (enemyData.agility + i) % EnemyActions.length;
        acts.push(new EnemyActions[idx](new shot()));
    }

    for (var i = 0; i < numBrrg; i++) {
        var idx     = (enemyData.skillpoint + i * (enemyData.skills.length + 1)) % EnemyBarrages.length;
        var brrgCls = EnemyBarrages[idx];
        var frameColor;

        switch(idx % 3) {
        case 0:
            frameColor = "rgba(128,32,32,0.7)";
            break;
        case 1:
            frameColor = "rgba(32,128,32,0.7)";
            break;
        default:
            frameColor = "rgba(64,64,128,0.7)";
        }

        for (var k = 0; k < numBlt; k++) {
            var iidx = (enemyData.skills.length + i + k) % EnemyBullets.length;
            brrgs.push(
                new brrgCls(
                    EnemyBullets[iidx],
                    bulletSize,
                    "#FF3",
                    {"style": "rect", "color": frameColor,
                     "width": bulletFrameWidth, "height": bulletFrameHeight},
                    bulletInterval,
                    bulletSpeed,
                    bulletWay
                )
            );
        }
    }

    var size  = Math.ceil((System.screen.width / 2) * (1 / enemyData.defense));
    var enemy = new Trooper(
        enemyData.name,
        new actList(acts),
        System.enemyImages[enemyData.hitpoint % System.enemyImages.length],
        size,
        size,
        "#F33",
        "#F33",
        Math.floor(Math.random() * System.screen.width),
        Math.floor(Math.random() * (System.screen.height / 4)),
        System.screen.width,
        System.screen.height,
        Math.floor(enemyData.hitpoint / 25) + 1,
        Math.log(enemyData.agility + 0.1) * 3,
        0,
        ["rgba(255,0,0,0.3)", "rgba(0,0,255,0.3)"],
        brrgs
    );
    enemy.registerCallback("damaged", function() {playSound("se_damage_enemy")});

    System.enemies.push(enemy);
}


/*
 *  Main loop
 */
function mainLoop() {
    // clear screen
    drawScreen(
        System.screen.ctx,
        "source-over",
        "rgba(8,8,8,0.8)",
        System.screen.width,
        System.screen.height
    );

    // update background objects
    updateBackground(
        System.screen.ctx,
        System.screen.width,
        System.screen.height,
        1, "#CAF", 10
    );

    switchStageBgm();

    // draw score
    var scoreNames = Object.keys(System.score).sort();
    for (var i = 0; i < scoreNames.length; i++) {
        var name = scoreNames[i];
        drawString(
            System.screen.ctx,
            "source-over",
            name + " score " + System.score[name],
            (System.screen.width - 10),
            i * 16 + 15,
            "#ACF", "9pt monospace", "right"
        );
    }

    // update/draw troopers
    for (var i = 0; i < System.players.length; i++) {
        var player = System.players[i];

        updateTrooper(player, "enemies");

        drawLifeGauge(
            System.screen.ctx,
            "lighter",
            player,
            10,
            (System.screen.height - 20) - (i * 33),
            System.screen.width
        );

        drawString(
            System.screen.ctx,
            "source-over",
            player.name,
            10,
            (System.screen.height - 23) - (i * 33),
            "#ACF", "9pt monospace", "left"
        );
    }

    // update/draw enemies
    for (var i = 0; i < System.enemies.length; i++) {
        var enemy = System.enemies[i];

        updateTrooper(enemy, "players");

        drawLifeGauge(
            System.screen.ctx,
            "lighter",
            enemy, 10, i * 33 + 10,
            System.screen.width
        );

        drawString(
            System.screen.ctx,
            "source-over",
            enemy.name, 10, i * 33 + 33,
            "#FCA", "9pt monospace", "left"
        );
    }

    updateDeathPieces(System.screen.ctx,
                      System.screen.width,
                      System.screen.height);

    if (!System.players.length) {
        drawString(
            System.screen.ctx, "source-over",
            "GAME OVER",
            System.screen.width / 2,
            System.screen.height / 2,
            "#ACF", "24pt monospace", "center"
        );
    }
}


/*
 *  Initializer
 */
function initGame(canvas, msg, playerData) {
    System.screen.canvas = canvas;
    System.message       = msg;
    System.screen.ctx    = System.screen.canvas.getContext("2d");
    System.screen.width  = System.screen.canvas.width;
    System.screen.height = System.screen.canvas.height;
    System.gameOver      = false;

    System.screen.ctx.globalCompositeOperation = "lighter";

    if (System.mainIntervalId) {
        clearInterval(System.mainIntervalId);
        System.mainIntervalId   = 0;
        System.players          = new Array();
        System.enemies          = new Array();
        System.backgroundObject = new Array();
        System.deathPieces      = new Array();
    }

    var trooper = new Trooper(
        playerData.name,
        new ActionList([new ManualAction(new ManualShot())]),
        playerData.image,
        playerData.size,
        playerData.hitsize,
        "#33F",
        "#F33",
        System.screen.width / 2,
        System.screen.height - System.screen.height / 7,
        System.screen.width,
        System.screen.height,
        playerData.hitpoint,
        playerData.speed,
        playerData.numbombs,
        ["rgba(255,0,0,0.3)", "rgba(0,0,255,0.3)"],
        [new LinerBarrage(YExtendBullet,
                          playerData.shotsize,
                          "rgba(64,64,128,0.7)",
                          null,
                          playerData.shotinterval,
                          playerData.shotspeed,
                          playerData.shotlevel,
                          -0.5),
         new LinerBarrage(YExtendBullet,
                          playerData.shotsize,
                          "rgba(64,64,128,0.7)",
                          null,
                          playerData.shotinterval,
                          playerData.shotspeed,
                          playerData.shotlevel,
                          0.5),
         new CircularBarrage(LinerBullet,
                          playerData.shotsize,
                          "rgba(64,64,128,0.7)",
                          null,
                          playerData.shotinterval,
                          playerData.shotspeed,
                          playerData.shotlevel + 2,
                          -0.5)]
    );
    trooper.registerCallback("addBomb", function(){playSound("se_bomb")});
    trooper.registerCallback("damaged", function(){playSound("se_damage_player")});

    System.players.push(trooper);

    for (var i = 0; i < System.players.length; i++) {
        System.score[System.players[i].name] = 0;
    }

    drawScreen(
        System.screen.ctx,
        "source-over",
        "rgba(0,0,0,1)",
        System.screen.width,
        System.screen.height
    );

    document.onkeydown  = function (ev) { setKeyDown(ev.keyCode); };
    document.onkeyup    = function (ev) { setKeyUp(ev.keyCode); };
    document.onkeypress = function (ev) { setKeyPress(ev.charCode); };

    System.mainIntervalId = setInterval(mainLoop, 20);
}
