// vim:syntax=javascript

// Central game driver

class LevelClip extends MovieClip
{
    var _initial_guypos : Point;
    var _initial_enemies : /*EnemyInit*/ Array;
    var _initial_filled : /*Rectangle*/ Array;
    var _initial_blasts : Number;
    var _exitpos : Point;

    var _level_width : Number;
    var _level_height : Number;
    var _blocks_wide : Number;
    var _blocks_high : Number;
    var _block_size : Number;

    var _guy : Guy;
    var _actors : /*Actor*/ Array;
    var _levelgrid : /*Array*/ Array;   // true if block is filled
    var _blasts_left : Number;
    var _blast_meter_mc : BlastMeter;

    static var DEATH_PAUSE : Number = 50;
    var _guy_dead_frames : Number;

    var _home_in_mode : Boolean;
    var _descend_frames : Number;

    var danger_mc : MovieClip;

    var _whenDone : Function;

    static var disable_fcn : Function = function () {
        this.enabled = false;
    };

    function nameOfBlockMC(x : Number, y : Number) : String {
        return "block_"+x+"_"+y+"_mc";
    }
    function depthOfBlockMC(x : Number, y : Number) : Number {
        return x*_blocks_high+y;
    }
    function depthOfExitMC() : Number {
        return depthOfBlockMC(_blocks_wide, _blocks_high);
    }
    function depthOfDangerMC() : Number {
        return depthOfBlockMC(_blocks_wide, _blocks_high+1);
    }

    function resetLevel() {
        _guy_dead_frames = 0;
        _home_in_mode = false;
        _descend_frames = 0;
        _blasts_left = _initial_blasts;
        _blast_meter_mc.setBlasts(_blasts_left);

        /* remove any existing actors */
        while (_actors.length > 0) {
            var tokill = _actors.pop();
            tokill.remove();
        }
        _guy = null;

        /* set grid to intially all clear */
        for (var x = 0; x < _blocks_wide; x++) {
            var gridrow = _levelgrid[x];
            for (var y = 0; y < _blocks_high; y++) {
                gridrow[y] = false;
                attachMovie("floor", nameOfBlockMC(x,y), depthOfBlockMC(x,y),
                    {_x:x*_block_size,_y:y*_block_size,onLoad:disable_fcn});
                //trace("floor at ("+x+","+y+") = "+this[nameOfBlockMC(x,y)]);
            }
        }

        /* fill filled ranges */
        var fill_range_count = _initial_filled.length;
        for (var i = 0; i < fill_range_count; i++) {
            var fill_range = _initial_filled[i];
            var x_start = fill_range.x;
            var x_limit = fill_range.x + fill_range.width;
            var y_start = fill_range.y;
            var y_limit = fill_range.y + fill_range.height;

            for (var x = x_start; x < x_limit; x++) {
                var gridrow = _levelgrid[x];

                for (var y = y_start; y < y_limit; y++) {
                    gridrow[y] = true;
                    attachMovie("wall", nameOfBlockMC(x,y), depthOfBlockMC(x,y),
                        {_x:x*_block_size,_y:y*_block_size,onLoad:disable_fcn});
                    //trace("wall at ("+x+","+y+") = "+this[nameOfBlockMC(x,y)]);
                }
            }
        }

        /* add exit */
        attachMovie("stair", "exit_mc", depthOfExitMC(),
            {_x:_exitpos.x, _y:_exitpos.y});

        /* add guy's aura of menace */
        attachMovie("danger_zone", "danger_mc", depthOfDangerMC(), {_x:_initial_guypos.x+Guy.GUY_SIZE/2-Guy.DANGER_ZONE,_y:_initial_guypos.y+Guy.GUY_SIZE/2-Guy.DANGER_ZONE,_alpha:25});

        /* add guy */
        _guy = new Guy(this, _initial_guypos);
        _actors.push(_guy);

        /* add enemies */
        var enemy_count = _initial_enemies.length;
        for (var i = 0; i < enemy_count; i++) {
            _actors.push(Enemy.makeEnemy(this, _initial_enemies[i]));
        }
    }

    function isPixelFilled(x : Number, y : Number) : Boolean {
        return _levelgrid[Math.floor(x/_block_size)][Math.floor(y/_block_size)];
    }

    // only check for collision with walls
    function checkPixelCollision(min_x : Number, min_y : Number, width : Number, height : Number) : Boolean {
        // assume positive width and height

        var max_x = min_x + width - 1;
        var max_y = min_y + height - 1;

        // check for offscreen
        if (min_x < 0 || min_y < 0 ||
            max_x >= _level_width || max_y >= _level_height) {
            return true;
        }

        // check for corners being in a filled square
        if (isPixelFilled(min_x,min_y) ||
            isPixelFilled(max_x,min_y) ||
            isPixelFilled(min_x,max_y) ||
            isPixelFilled(max_x,max_y)) {
            return true;
        }

        return false;
    }

    function checkActorCollision(a1 : Actor, a2 : Actor) : Boolean {
        var a1_min_x = a1.x;
        var a1_min_y = a1.y;
        var a1_max_x = a1.x+a1.size-1;
        var a1_max_y = a1.y+a1.size-1;

        var a2_min_x = a2.x;
        var a2_min_y = a2.y;
        var a2_max_x = a2.x+a1.size-1;
        var a2_max_y = a2.y+a1.size-1;

        var int_min_x = Math.max(a1_min_x, a2_min_x);
        var int_min_y = Math.max(a1_min_y, a2_min_y);
        var int_max_x = Math.min(a1_max_x, a2_max_x);
        var int_max_y = Math.min(a1_max_y, a2_max_y);

        return int_min_x <= int_max_x && int_min_y <= int_max_y;
    }

    // handle collision between actors
    function handleActorCollision(a1 : Actor, a2 : Actor) {
        if (a1 == _guy && a2.killsGuy() ||
            a2 == _guy && a1.killsGuy()) {
            _guy.setDead();
        }
    }

    function doGuyInRadius(a_center : Actor, r : Number, f : Function) {
        var center_x = a_center.x + a_center.size/2;
        var center_y = a_center.y + a_center.size/2;
        if (_guy.alive && _guy.isWithinRadius(center_x, center_y, r)) {
            f(_guy);
        }
    }
    function doActorsInRadius(a_center : Actor, r : Number, f : Function) {
        var actor_count = _actors.length;
        var center_x = a_center.x + a_center.size/2;
        var center_y = a_center.y + a_center.size/2;

        for (var i = 0; i < actor_count; i++) {
            if (_actors[i].isWithinRadius(center_x, center_y, r)) {
                f(_actors[i]);
            }
        }
    }

    function clearBlocksInRadius(cx : Number, cy : Number, r : Number) {
        //trace("clearBlocksInRadius ("+cx+","+cy+") r="+r);
        for (var x = 0; x < _blocks_wide; x++) {
            var grid_row = _levelgrid[x];
            for (var y = 0; y < _blocks_high; y++) {
                if (grid_row[y]) {
                    //trace("consider ("+(x*_block_size)+","+(y*_block_size)+")");
                    if (Actor.isRectangleWithinRadius(
                        cx, cy, 
                        x*_block_size, y*_block_size,
                        _block_size, _block_size, r)) {
                        //trace("got ("+x+","+y+")");
                        
                        grid_row[y] = false;
                        attachMovie("broken_wall",
                            nameOfBlockMC(x,y), depthOfBlockMC(x,y),
                            {_x:x*_block_size,_y:y*_block_size,
                             onLoad:disable_fcn});
                    }
                }
            }
        }
    }

    function canAsplode(a : Actor) : Boolean {
        if (!_guy.alive) {
            return false;
        }

        if (a == _guy) {
            // can always suicide
            return true;
        }

        if (_blasts_left == 0) {
            return false;
        }

        if (a.isWithinRadius(
            _guy.x+_guy.size/2,_guy.y+_guy.size/2,Guy.DANGER_ZONE)) {
            _blasts_left --;
            _blast_meter_mc.setBlasts(_blasts_left);
            _guy.doPoint(a.x+a.size/2,a.y+a.size/2);
            return true;
        }

        return false;
    }

    function onLoad() {
        _actors = new Array();
        _levelgrid = new Array();
        for (var i = 0; i < _blocks_wide; i++) {
            _levelgrid[i] = new Array();
        }

        resetLevel();
    }

    function onEnterFrame() {
        if (_home_in_mode) {

            if (_descend_frames > 0 ||
                (_guy.x == _exitpos.x && _guy.y == _exitpos.y)) {
                if (_descend_frames == 0) {
                    var mc = attachMovie("stair_mask", "stair_mask_mc", getNextHighestDepth(), {_x:_guy.x,_y:_guy.y,_guy:_guy});
                }
                _guy.moveTo(_guy.x,_guy.y+1,Actor.DIR_DOWN);

                _descend_frames ++;
                if (_descend_frames == _guy.size) {
                    removeMovieClip();
                }
            } else {
                var next_x = int(_guy.x/Guy.GUY_SPEED)*Guy.GUY_SPEED;
                var next_y = int(_guy.y/Guy.GUY_SPEED)*Guy.GUY_SPEED;
                if (next_x < _exitpos.x) {
                    next_x += Guy.GUY_SPEED;
                } else if (next_x > _exitpos.x) {
                    next_x -= Guy.GUY_SPEED;
                }
                if (next_y < _exitpos.y) {
                    next_y += Guy.GUY_SPEED;
                } else if (next_y > _exitpos.y) {
                    next_y -= Guy.GUY_SPEED;
                }

                _guy.moveTo(next_x, next_y, _guy.dir);
            }

            return;
        }

        var actor_count = _actors.length;
        for (var i = 0; i < actor_count; i++) {
            var actor = _actors[i];

            if (actor.alive) {
                actor.doFrame();
            }
        }

        var need_reaping = false;

        // check for collisions between actors
        for (var i = 0; i < actor_count; i++) {
            var a1 = _actors[i];

            // turns out we really don't need general purpose collision for
            // this game
            /*
            // only check where j > i, this avoids checking a given pair twice
            for (var j = i+1; j < actor_count; j++) {
                var a2 = _actors[j];

                if (checkActorCollision(a1, a2)) {
                    handleActorCollision(a1, a2);
                }
            }
            */

            if (!a1.alive) {
                need_reaping = true;
            }
        }

        // handle dead
        if (need_reaping) {
            var _old_actors : /*Actor*/ Array = _actors;
            _actors = new Array();

            for (var i = 0; i < actor_count; i++) {
                var old_actor : Actor = _old_actors[0];
                _old_actors.shift();
                if (old_actor.alive) {
                    // copy live actors into new array
                    _actors.push(old_actor);
                } else {
                    // handle any post-death spawn
                    if (old_actor.onDeath != null) {
                        var spawned = old_actor.onDeath();

                        if (spawned != null) {
                            _actors.push(spawned);
                        }
                    }

                    old_actor.remove();
                }
            }
        }

        // see what guy's up to
        if (_guy.alive) {
            var exitcenterx = _exitpos.x+_block_size/2;
            var exitcentery = _exitpos.y+_block_size/2;

            if (exitcenterx >= _guy.x && exitcenterx < _guy.x+_guy.size &&
                exitcentery >= _guy.y && exitcentery < _guy.y+_guy.size) {

                var whenDoneLocal = _whenDone
                onUnload = function () {
                    whenDoneLocal();
                };

                _home_in_mode = true;

            }

            danger_mc._x = _guy.x + _guy.size/2 - Guy.DANGER_ZONE;
            danger_mc._y = _guy.y + _guy.size/2 - Guy.DANGER_ZONE;
        } else {
            if (_guy_dead_frames == 0) {
                danger_mc._alpha = 0;
            }
            _guy_dead_frames ++;
            if (_guy_dead_frames >= DEATH_PAUSE) {
                resetLevel();
            }
        }

        //trace("width="+_width+", height="+_height);
    }
}
