/*
* $Id: tiles.js 177 2010-08-10 18:51:54Z victor $
*/

var tooDeeHash = function(max)
{
    this.max   = max || 2000;
    this.table = [];
}

tooDeeHash.prototype = {
    
    Lookup: function( inKeyA, inKeyB )
    {    
        var hashKey = this._computeHash( inKeyA, inKeyB );
        return this.table[ hashKey ] || false;
    },

    Insert: function( inKeyA, inKeyB, inItem )
    {
        var hashKey = this._computeHash( inKeyA, inKeyB );

        this.table[ hashKey ] = inItem;
        
        return hashKey;
    },

    _computeHash: function( inKeyA, inKeyB )
    {    
        var hashKey = ( inKeyA * 734727 + inKeyB * 263474 ) % this.max;
        if( hashKey < 0 ) {
            hashKey += this.max;
            }
        return hashKey;
    }
    
}

// from http://freespace.virgin.net/hugo.elias/models/m_perlin.htm
// via Rhorer's original Passage
// 
function tooDeePerlin2d(x,y)
{
    var round = Math.round, pow = Math.pow, cos = Math.cos, PI = Math.PI;
    
    function Noise(x,y)
    {
        var n = x + y * 57;
        var n = (n << 13) ^ n;
        return ( 1.0 - ( (n * (n * n * 15731 + 789221) + 1376312589) & 0x7fffffff) / 1073741824.0);    
    }

    function SmoothedNoise(x, y)
    {
        var corners = ( Noise(x-1, y-1)+Noise(x+1, y-1)+Noise(x-1, y+1)+Noise(x+1, y+1) ) / 16
        var sides   = ( Noise(x-1, y)  +Noise(x+1, y)  +Noise(x, y-1)  +Noise(x, y+1) ) /  8
        var center  =  Noise(x, y) / 4
        return corners + sides + center
    }
    
    function Interpolate(a, b, x)
    {
        var ft = x * PI;
        var f = (1 - cos(ft)) * 0.5
        
        return  a*(1-f) + b*f
    }
    
    function InterpolatedNoise(x, y)
    {
        var integer_X    = round(x)
        var fractional_X = x - integer_X
        
        var integer_Y    = round(y)
        var fractional_Y = y - integer_Y
        
        var v1 = SmoothedNoise(integer_X,     integer_Y)
        var v2 = SmoothedNoise(integer_X + 1, integer_Y)
        var v3 = SmoothedNoise(integer_X,     integer_Y + 1)
        var v4 = SmoothedNoise(integer_X + 1, integer_Y + 1)
        
        var i1 = Interpolate(v1 , v2 , fractional_X)
        var i2 = Interpolate(v3 , v4 , fractional_X)
        
        return Interpolate(i1 , i2 , fractional_Y)
    }    


    var total = 0
    var p = 1/4; // persistence
    var n = 3;   // Number_Of_Octaves - 1
    
    for( var i = 0; i < n; i++ )
    {
        var frequency = pow(2,i);
        var amplitude = pow(p,i);
    
        total += InterpolatedNoise(x * frequency, y * frequency) * amplitude
    }
    
    return total    
}

var tooDeeTileManager = function()
{
    this.hash = new tooDeeHash();
}

tooDeeTileManager.prototype = {
    
    // in case you're wondering: _populate
    // was clocked in at <10ms on a MacBook
    // and it's called
    // exactly once per session. 
    _populate: function()
    {
        var threshold = 0;
        var minY = 2, maxY = 20, minX = 2, maxX = 100;

        // pass 1:
        // start with a grid, no more dense than 3x3
        for( var y = minY; y < maxY; y+=2 )
        {
            threshold = (y - 1) / maxY;
            
            for( var x = minX; x < maxX; x+=2 )
            {
                if( (x&3)|(y&3) > 0 )
                {
                    var randValue = tooDeePerlin2d(x,y);
                    if( randValue > threshold )
                        this.hash.Insert(x,y,{ });
                }
                
            }
        }

        // pass 2:
        // fill in surrounded blocks
        for( y = minY; y < maxY; y+=2 )
        {
            for( x = minX; x < maxX; x+=2 )
            {
                if( !this.hash.Lookup(x,y) && this._allNeighborsBlocked(x,y) )
                {
                    this.hash.Insert(x,y,{ });
                }
            }
        }
        
        var treSides = [];
        
        // pass 3:
        // mark tile sides
        for( y = minY; y < maxY; y+=2 )
        {
            for( x = minX; x < maxX; x+=2 )
            {
                var tile = this.hash.Lookup(x,y);
                if( tile )
                {
                    var neighborsBlockedBinary = 0, numSides = 0;
                    
                    if( this.hash.Lookup( x, y - 2 ) ) {
                        // top
                        neighborsBlockedBinary = neighborsBlockedBinary | 1;
                        ++numSides;
                        }
                    if( this.hash.Lookup( x + 2, y ) ) {
                        // right
                        neighborsBlockedBinary = neighborsBlockedBinary | 1 << 1;
                        ++numSides;
                        }
                    if( this.hash.Lookup( x, y + 2 ) ) {
                        // bottom
                        neighborsBlockedBinary = neighborsBlockedBinary | 1 << 2;
                        ++numSides;
                        }
                    if( this.hash.Lookup( x - 2, y ) ) {
                        // left
                        neighborsBlockedBinary = neighborsBlockedBinary | 1 << 3;
                        ++numSides;
                        }
                
                    // skip empty tile, treat as tile index
                    tile.i = neighborsBlockedBinary + 1;
                    
                    if( numSides == 3 )
                        treSides.push( [x,y] );
                }
            }
        }
        
        this.threeSidesBlocked = treSides; // use this for treasure/reward
    },
    
    _allNeighborsBlocked: function(x,y)
    {
        var H = this.hash;
        
        function _allBlocked(x,y)
        {
            return isBlocked(x,y-2) && isBlocked(x+2,y) && isBlocked(x-2,y) && isBlocked(x,y+2);            
        }
        
        function isBlocked(x,y)
        {
            // for now...
            var maxX = 100, maxY = 20;
            
            if( x <= 0 || y <= 0 || x >= maxX || y >= maxY )
            {
                return true;
            }
            
            return H.Lookup(x,y); //  || _allBlocked(x,y);
        }
        
        return _allBlocked(x,y);
        
    }
}

var tooDeeTiles = function(options)
{
    var defaults = {
        visible: true,
        zOrder: 0,
        name: 'tiles.floor'
    };
    
    tooDeeSprite.apply( this, [ tooDeeOptMix({},defaults,options) ]);
    
    this.currentTileSet = 0;
    this.nextTileSet = 1;
    this.numTileSets = 18;
    
    this.fadeInAlpha = 0.2;
    this.crossFadeAlpha = 1.0;
    
    this.imgReady = false;
    this.img = new Image();
    var me = this;
    this.img.onload = function() {
        me.imgReady = true;
    };
    this.img.src = '/images/original-tiles.png';
    this.drawMode = 'full';
    this.lastScroll = { x: -1, y: -1 }
    
    this.world.background = 'transparent'; // otherwise he does a clearRect()    
}

tooDeeMixin( tooDeeTiles, tooDeeSprite, {
   
    dbg: 'tooDeeTiles',
    
    /*
        Have you ever been in so much pain you puked and
        passed out? Well, now I have. I walked around for
        over a year with an oxalate crystal growing in
        my kidney until, finally, at 1.2cm it decided it
        needed more space so it started scraping my insides
        to let me know it wanted out.
        
        I wanted it out too, once I knew what was wrong -
        which by the way, was not the most pleasant process.
        
        Christmas, Barcelona. Ambulance comes to pick me
        up and I use the two words I've learned are key to
        my situation: "dolor aqui." The hospital is great
        but it takes a few hours to convince them I am in
        too much to pain to sit OR stand. After another
        hour (or two?) on a gourney the young, tall, bushy
        haired doctor pulls a computer on a rolling desk
        up to me and starts using Google translate to
        tell me what's wrong. After much stuttering -
        "ÀC—mo se dice...? ...GAS!" (rhymes with Spanish
        "m‡s.")
        
        Oh, for fuck's sake, I've had gas in my life, "NO
        GAS!" I say.
        
        "S’?" he said, cocking his patronizing head.
        
        "See, no gas, no fucking way."
        
        "Ho kay," he said, pushing the computer away
        and getting up to walk away. Yet, I distinctly
        heard him say "S’, gas" as he turned the corner
        on the curtain.
        
        It took until dawn and the next shift of doctors
        before a short, solid doctor, wearing a smock
        too sizes to small for her stocky build threw
        the curtain open and introduced herself "Hola,
        Mister Stone! You have a kidney stone!" She
        was downright exicted.
        
        "Huh?"
        
        She hands me a little plastic pee cup, points
        me at the john and off I go.
        
        Once back on the gourney she places the cup
        on the counter, opens it up, sticks a strip
        of paper in it (ph test?) After a few seconds
        she looks up, glowing: "Kidney stone!"
        
    */
    TestCollision: function()
    {
        var hash = this.tileManager.hash,
            kc   = this.world.KEY_CODES,
            ret  = false,
            me   = this;
        
        this.world.movingSprites.Each( function(sprite,i,iter) {
            if( !sprite.currentFigure )
                return;

            var x, y, dir;
                
            switch( sprite.currentFigure )
            {
                case kc.left:
                    x = (sprite.center.x - 1) & ~1;
                    y = sprite.center.y & ~1;
                    dir = 'E';
                    break;
                case kc.right:
                    x = (sprite.center.x + 1) & ~1;
                    y = sprite.center.y & ~1;
                    dir = 'W';
                    break;
                case kc.up:
                    x = sprite.center.x & ~1;
                    y = (sprite.center.y - 1) & ~1;
                    dir = 'S';
                    break;
                case kc.down:
                    x = sprite.center.x & ~1;
                    y = (sprite.center.y + 1) & ~1;
                    dir = 'N'
                    break;
                default:
                    return;
            }
            
            if( hash.Lookup(x,y) !== false )
            {
                sprite.CollideNotify(me,dir);
                iter.Stop();
                ret = true;
            }
            
        });
    },
    
    Draw: function()
    {
        if( !this.imgReady )
            return;
        
        var  ctx = this.GetContext();

        if( this.fadeInAlpha < 1.0 )
        {
            ctx.save();
            ctx.globalAlpha = this.fadeInAlpha;
            this._doDraw(ctx,true,this.currentTileSet);
            ctx.restore();
            this.fadeInAlpha += 0.08
            if( this.fadeInAlpha >= 1.0 )
            {
                var seconds = this.world.config.clockResolution * 10;
                this.world.Bind('preStep',this._bumpTileSets,this,seconds);
            }
        }
        else if( this.crossFadeAlpha < 1.0 )
        {
            ctx.save();
            //ctx.globalApha = this.crossFadeAlpha;
            this._doDraw(ctx,true,this.currentTileSet);
            ctx.globalAlpha = this.crossFadeAlpha <= 0.05 ? 1.0 : 1.0 - this.crossFadeAlpha;
            this._doDraw(ctx,true,this.nextTileSet);
            ctx.restore();
            this.crossFadeAlpha *= 0.7;
            if( this.crossFadeAlpha <= 0.03 )
            {
                this.crossFadeAlpha = 1.0;
                this.currentTileSet = ++this.currentTileSet % this.numTileSets;
                this.nextTileSet    = ++this.nextTileSet % this.numTileSets;
            }
        }
        else
        {
            this._doDraw(ctx,false,this.currentTileSet);
        }
    },

    _bumpTileSets: function()
    {
        this.crossFadeAlpha = 0.9;
    },
    
    _doDraw: function(ctx,fullscreen,tileSet)
    {
        // round up to the next pixel, otherwise
        // a gap can occur
        
        var wid     = 2 + this.world.MeterPerPixel(),
            hash    = this.tileManager.hash,
            srcX    = tileSet * (32+4), // a 4 pixel gap between tiles
            img     = this.img,
            floor   = Math.floor,
            abs     = Math.abs;

        var start, x, y, w, h;

        function drawTiles()
        {
            while( x < w )
            {
                while( y < h )
                {
                    var tile = hash.Lookup(x,y),
                        srcY = (tile ? tile.i : 0) * (32+4);
            
                    ctx.drawImage(img,srcX,srcY,32,32,x,y,wid,wid);
                    y += 2;
                }
                y = start;
                x += 2;
            }
        }
        
        if( this.lastScroll.x != this.world.viewport.x ||
            this.lastScroll.y != this.world.viewport.y ||
            fullscreen )
        {
            var dim = this.world.CanvasDim();
            y = start = abs(this.world.viewport.y) & ~1; // round to the tile start
            x = abs(this.world.viewport.x) & ~1;
            w = x + dim.x + 1;
            h = y + dim.y + 1;
            
            drawTiles();
            
            this.lastScroll.x = this.world.viewport.x;
            this.lastScroll.y = this.world.viewport.y;
            
        }
        else
        {
            this.world.movingSprites.Each( function(sprite) {
                var sdim = sprite.dimensions;
                x = sdim.UL.x & ~1;
                y = start = sdim.UL.y & ~1;
                w = (sdim.UL.x + sdim.width  + 2) & ~1;
                h = (sdim.UL.y + sdim.height + 2) & ~1;
                
                drawTiles();
            });
        }
   }
   
});

