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



var tooDeeBodyPart = function( options )
{
    tooDeeSprite.apply( this, [options] );
    
    this.framePosition = new tooDeeVec2();
    
    this.drawPos = new tooDeeVec2(); // this changes a lot but there's
                                     // no reason to do a 'new' every
                                     // Draw()
};

tooDeeMixin(tooDeeBodyPart, tooDeeSprite,
{
    dbg: 'tooDeeBodyPart',

    GetFrame: function()
    {
        return this.parent.animator.CurrentFrame( this.partInfo.frames );
    },

    ResetFrame: function()
    {
        this.framePosition.Set(this.partInfo.pos);
    },
    
    MoveByFrame: function()
    {
        var F = this.GetFrame();
        this.framePosition.Add( F.dxy);
    },
    
    Draw: function()
    {
        var F = this.GetFrame();
        
        if( !F )
            return; // this really isn't supposed to happen...

        var myCharacter = this.parent.parent;
        
        var isFemale = myCharacter.gender == this.world.GENDER.female;
        var len = F.d / 2;
        var rot = F.a;        
        var qu  = len * 0.75;
        var wid = len * 0.4;
        this.drawPos.Set(this.framePosition).Add(myCharacter.center);
        
        if( isFemale )
            wid *= 0.6;
            
        var controlPoints = [        //                    o       <= pos.x, pos.y - len
            { x:  wid, y: -qu },     //  -wid,-qu  =>   3     0    <= wid, -qu
            { x:  wid, y:  qu },     //             
            { x: -wid, y:  qu },     //
            { x: -wid, y: -qu }      //  -wid, qu  =>   2     1    <= wid, qu
        ];                           //                    o       <= pos.x, pos.y + len
        
        var cp = controlPoints;
        var context = this.GetContext();

        var oldStrokeStyle = context.strokeStyle;
        var oldFillStyle = context.fillStyle;
        var oldLineWidth = context.lineWidth;
        
        context.strokeStyle = 'black';
        context.fillStyle = this.partInfo.color;
        context.lineWidth = this.world.MeterPerPixel();
        
        context.save();
        context.translate( this.drawPos.x, this.drawPos.y );
        context.rotate(rot);
        context.beginPath();
        context.moveTo( 0, -len );
        context.bezierCurveTo( cp[0].x, cp[0].y, cp[1].x, cp[1].y, 0,  len );
        context.bezierCurveTo( cp[2].x, cp[2].y, cp[3].x, cp[3].y, 0, -len );
        context.fill();

        context.beginPath();
        if( (this.partInfo.ss & 1) != 0 ) // stroke the front?
        {
            context.moveTo( 0, len )
            context.bezierCurveTo( cp[1].x, cp[1].y, cp[0].x, cp[0].y, 0, -len );
        }
        
        if( (this.partInfo.ss & 2) != 0 ) // stroke the back?
        {
            context.moveTo( 0, -len );
            context.bezierCurveTo( cp[3].x, cp[3].y, cp[2].x, cp[2].y, 0,  len );
        }
        
        if( (this.partInfo.ss & 4) != 0 ) // stroke the hair? (heh)
        {
            context.moveTo( 0, -len );
            switch( this.parent.frameInfo.dir )
            {
                case 'R': // facing right
                    context.bezierCurveTo( cp[3].x, cp[3].y, cp[2].x, cp[2].y, wid*-8,  len );
                    break;
                case 'L': // facing left
                    context.bezierCurveTo( cp[0].x, cp[0].y, cp[1].x, cp[1].y, wid*8,  len );
                    break;
                case 'F': // facing foward
                    context.bezierCurveTo( cp[0].x, cp[0].y, cp[1].x, cp[1].y, wid*8,  len );
                    break;
                case 'B': // facing foward
                    break;
            }
        }
        
        context.stroke();

        context.restore(); // turn off translation/rotation
                
        context.strokeStyle = oldStrokeStyle;
        context.fillStyle   = oldFillStyle;
        context.lineWidth   = oldLineWidth;
        
    },
    
    Destroy: function()
    {
        tooDeeSprite.prototype.Destroy.apply(this);  
        this.partInfo = null;
    }
});


var tooDeeFigure = function( options )
{
    tooDeeSprite.apply( this, [options] );

    var FI = this.frameInfo;
    this.animator  = new tooDeeAnimation(this.world, FI.numFrames, FI.fps );

    this.advanceToken = this.animator.Bind('advance', this.Advance, this );
    
    // the animation scheme is based on relative positions so every now
    // and then we have to reset to the positions to the start point
    // because otherwise the figure will drift apart
    
    this.cycleToken = this.animator.Bind('cycle', this.children.Propogate, this.children, 4, ['ResetFrame']);

    this.doesMove = (this.speed.x != 0) || (this.speed.y != 0);
                    
    for( var name in FI.parts )
    {
        var partInfo = FI.parts[name];
    
        var spriteInfo = {
            zOrder: partInfo.z,
            partInfo: partInfo,
            parent: this,
            world: this.world,
            name: this.name + '.' + name
        };
        
        this.AddChild( new tooDeeBodyPart(spriteInfo) );
    }

};

tooDeeMixin(tooDeeFigure, tooDeeSprite, 
{
    dbg: 'tooDeeFigure',

    Destroy: function()
    {
        this.animator.Destroy();
        tooDeeSprite.prototype.Destroy.apply(this);
        this.animator = null;
    },

    Advance: function()
    {
        this.children.Propogate( 'MoveByFrame' );
        
        if( this.doesMove && this.parent.TrackMove )
        {
            this.parent.TrackMove.apply( this.parent, [ this.speed ] );
        }        
    },
    
    Freeze: function(flag)
    {
        this.animator.Freeze(flag);
        if( !flag )
            this.children.Propogate( 'ResetFrame' );
    }    
});

function tooDeeCharacter( options )
{
    tooDeeSprite.apply( this, [options] );

    this.figures = [];
}

tooDeeMixin(tooDeeCharacter, tooDeeSprite, 
{
    dbg: 'tooDeeCharacter',
    
    TrackMove: null,
    
    IsPonderCode: function(code)
    {
        var kc = this.world.KEY_CODES;        
        
        switch(code)
        {
            case kc.ponder:
            case kc.toetap:
            case kc.turnRC: 
            case kc.turnLC:
            case kc.watchlk:
                return true;
        }
        
        return false;
    },
    
    CurrentFigure: function()
    {
        return this.figures[this.currentFigure];
    },
    
    KeySelect: function( keyCode  )
    {
        var fig = this.CurrentFigure();
        if( fig.frameInfo.keyb && this.IsValidKey(keyCode))
        {
            this.Select(keyCode);
            if( this.parent )
                this.parent.currentFigure = keyCode;    
        }        
    },
    
    Destroy: function()
    {
        this.children.Remove( this.figures[this.currentFigure] );
        tooDeeSprite.prototype.Destroy.apply(this);
        for( var i in this.figures )
        {
            this.figures[i].Destroy();
            delete this.figures[i];
        }
        this.figures = null;
    },

    Select: function(figureKey)
    {
        var me = this;
        if( !figureKey || this.currentFigure == figureKey )
            return false;
        
        // switching figures:
        
        var currSprite = this.figures[this.currentFigure];
        // turn the obsolete one off...
        currSprite.Freeze(true);
        this.children.Remove(currSprite);
        
        // this is the new active figure
        this.currentFigure = figureKey;
        currSprite = this.figures[figureKey];
        
        // position and activate it
        currSprite.Freeze( false );
        this.children.Append(currSprite);
        
        return true;
    },
    
    IsValidKey: function(figureKey)
    {
        return figureKey in this.figures;
    },
    
    DoPonder: function(pcode)
    {        
        if( !this.IsPonderCode(this.currentFigure) )
            this.Select(pcode);
    },
    
    GetBounds: function(B)
    {
        var cX = this.center.x;
        var cY = this.center.y;
        for( var i in this.figures )
        {
            var FI = this.figures[i].frameInfo;
            var fXmin, fXmax, fYmin, fYmax;
            fXmin = cX - FI.width/2;
            fXmax = fXmin + FI.width;
            fYmin = cY - FI.height/2;
            fYmax = fYmin + FI.height;
            if( fXmin < B.UL.x )
                B.UL.x = fXmin;
            if( fXmax > B.LR.x )
                B.LR.x = fXmax;
            if( fYmin < B.UL.y )
                B.UL.y = fYmin;
            if( fYmax > B.LR.y )
                B.LR.y = fYmax;
        }
     
        B.width  = B.LR.x - B.UL.x;
        B.height = B.LR.y - B.UL.y;
    }
    
});

/*
    What if our marriage was everything? I asked her. What
    if instead of just good or bad, it was both? You want to
    know, now that it's over, if the highs were real?
    Or were we just fooling ourselves? What if both of those
    things are true? There were times when the highs were
    real and other times when we were in denial about the
    reality. We were everything, we were in love, we were
    co-dependent enablers, we were the perfect couple, we
    totally sucked.
    
    I think she bought it. I'm sure I bought it.
*/
function tooDeeWaitingSpouse( options )
{
    var defaults = {
        visible: true,
        collidable: true,
        name: 'spouse.waiting',
        center: new tooDeeVec2(15,1),
        gender: options.world.GENDER.female
    };
    
    tooDeeCharacter.apply( this, [ tooDeeOptMix( {}, defaults, options || {}) ] );
    
    var code = this.currentFigure = this.world.KEY_CODES.ponder;
    
    this.figures[code] = new tooDeeFigure ( {
                zOrder: 10,
                name: 'fig.waiting.spouse',
                parent: this,
                world: this.world,
                speed: { x: 0, y: 0 },
                frameInfo: Fponder
            }
        );
    
    this.figures[code].Freeze(false);
    this.AddChild( this.figures[code] );
    
    this.dimensions = new tooDeeDimensions();
    this.dimensions.Set( this.center.x, this.center.y, this.center.x, this.center.y );
    this.GetBounds( this.dimensions );
}

tooDeeMixin(tooDeeWaitingSpouse, tooDeeCharacter, 
{
    dbg: 'tooDeeWaitingSpouse'
});

function tooDeeWalkingCharacter( options )
{
    tooDeeCharacter.apply( this, [options] );
    
    this.center = new tooDeeVec2();

    var me = this, kc = this.world.KEY_CODES, cc = this.world.CHARACTER_CODES;

    function createFigure( keycode, charcode, speedX, speedY, name)
    {
        me.figures[keycode] = new tooDeeFigure ( {
                    zOrder: 10,
                    name: name,
                    parent: me,
                    world: me.world,
                    speed: { x: speedX, y: speedY },
                    frameInfo: me.figureAnimations[charcode]
                }
            );
    }

    createFigure( kc.watchlk, cc.ponder1,       0, 0, 'watchLook' );
    createFigure( kc.toetap , cc.ponder2,       0, 0, 'toeTapper' );
    createFigure( kc.ponder , cc.ponder,        0, 0, 'ponderer' );
    createFigure( kc.turnRC , cc.turnToLeft,    0, 0, 'rightTurn' );
    createFigure( kc.turnLC , cc.turnToRight,   0, 0, 'leftTurn' );
    
    createFigure( kc.right,  cc.walkRight,       0.13,     0,  'walkRight' );
    createFigure( kc.left,   cc.walkLeft,       -0.13,     0,  'walkLeft' );
    createFigure( kc.up,     cc.walkToward,         0, -0.125, 'walkUp' );
    createFigure( kc.down,   cc.walkAway,           0,  0.125, 'walkDown' );

    // don't need these any more
    delete this.figureAnimations;
        
    this.currentFigure = kc.watchlk;
    this.figures[this.currentFigure].Freeze(false);
    this.children.Append( this.figures[this.currentFigure] );
    
    // the following setups for when the figure hit a wall and 'ponders' what
    // to do next.
    //
    // start with 'initial' ponder animation (like 'turnFromRight' so the figure
    // faces the player) which is played once, then select the actual 'ponder'
    // key coded figure which will play a few times, then switch to the toe tapper
    // figure for one iteration. These two go back and forth (tba: other figures
    // randomly thrown in.)
    //
    function hookTurner(code)
    {
        me.figures[code].animator.Bind('cycle',me.Select,me,1,[kc.ponder]);
    }
    
    hookTurner(kc.turnRC);
    hookTurner(kc.turnLC);

    function hookPonder( figureSprite, otherKey, pc )
    {
        figureSprite.animator.Bind('cycle',me.Select,me,pc,[otherKey]);
    }
    
    hookPonder( this.figures[kc.ponder], kc.toetap, 4 );
    hookPonder( this.figures[kc.toetap], kc.watchlk, 3 );
    hookPonder( this.figures[kc.watchlk], kc.ponder, 1 );
    
}

tooDeeMixin(tooDeeWalkingCharacter, tooDeeCharacter, 
{
    dbg: 'tooDeeWalkingCharacter'
});

function tooDeeCouple(options)
{
    var defaults = {
        collidable: true,
        stationary: false,
        visible: true,
        name: 'happyCouple'
    };
    
    var opts = tooDeeOptMix({},defaults,options);
    tooDeeSprite.apply( this, [opts] );

    // yes, this will be more dynamic (start with male, add female later)
    var character = new tooDeeWalkingCharacter( {
        gender: this.world.GENDER.male,
        name: 'male-character',
        parent: this,
        world: this.world,
        figureAnimations: [
                watchLook,     // 0 - ponder1
                toeTapper,     // 1 - ponder2
                ponder,        // 2 - ponder
                turnFromRight, // 3 - turn to the left
                turnFromLeft,  // 4 - turn to the right
                walkRight,     // 5 - walkRight
                walkLeft,      // 6 - walkLeft
                walkForward,   // 7 - walk toward
                walkForward    // 8 - walk away
            ]
    });
    
    this.children.Append( character );

    character.Select(this.world.KEY_CODES.right);
    
    // ok, this is a hack - only ever track the animation advances
    // of one of the two characters:
    
    var me = this;
    character.TrackMove = function( xy )
    {
        me.MoveBy(xy);
    }
    
    this.world.Bind( 'keystroke', this.OnKeyStroke, this );
    
    this.LineUpCharacters();
    this._getMaxCharacterBounds();

};

tooDeeMixin(tooDeeCouple, tooDeeSprite, 
{
    dbg: 'tooDeeCouple',

    OnKeyStroke: function(e)
    {
        this.children.Propogate( 'KeySelect', [ e.keyCode ] );
    },
    
    MoveBy: function( xy )
    {
        this.center.Add( xy );
        this.LineUpCharacters();
        this.dimensions.Offset( xy );
        this.world.SpriteMoved( this );        
    },

    Add: function()
    {
        var character = new tooDeeWalkingCharacter( {
            gender: this.world.GENDER.female,
            name: 'female-character',
            parent: this,
            world: this.world,
            figureAnimations: [
                Fponder,       // 0 - ponder1
                toeTapper,     // 1 - ponder2
                ponder,        // 2 - ponder
                turnFromRight, // 3 - turn to the left
                turnFromLeft,  // 4 - turn to the right
                FwalkRight,    // 5 - walkRight
                FwalkLeft,     // 6 - walkLeft
                walkForward,   // 7 - walk toward
                walkForward    // 8 - walk away    
            ]
        });
    
        this.children.Append( character );
        
        this.LineUpCharacters();
        this._getMaxCharacterBounds();
    },
    
    LineUpCharacters: function()
    {
        var me = this;
        var isCouple = this.children.list.length > 1;
        this.children.Each(
            function(character)
            {
                var y = me.center.y;
                var x = me.center.x;
                if( isCouple )
                {
                    if( character.gender == me.world.GENDER.male )
                    {
                        x -= 0.5;
                    }
                    else
                    {
                        x += 0.5;
                    }
                }
                character.center.Set(x,y);
            });
    },
    
    CollideNotify: function(notMe,side)
    {
        var notMeName = notMe.name.split('.');
        var kc = this.world.KEY_CODES;
        
        switch( notMeName[0] )
        {
            case 'tiles':
                {
                    var pcode;
                    switch(side)
                    {
                        case 'S':
                        case 'N':
                            pcode = kc.ponder;
                            break;
                        case 'W':
                            pcode = kc.turnRC;
                            break;
                        case 'E':
                            pcode = kc.turnLC;
                            break;
                    }
                    this.children.Propogate( 'DoPonder', [ pcode ] );
                }
                break;
            
            case 'wall':
                {
                    var wallType = notMeName[1];
                    var pcode, deltas = { x: 0, y: 0 };
                    switch(wallType)
                    {
                        case 'top':
                            deltas.y = 0.05;
                            pcode = kc.ponder;
                            break;
                        case 'bottom':
                            deltas.y = -0.05;
                            pcode = kc.ponder;
                            break;
                        case 'left':
                            deltas.x = -0.05;
                            pcode = kc.turnRC;
                            break;
                        case 'right':
                            deltas.x = 0.05;
                            pcode = kc.turnLC;
                            break;
                    }
                    this.children.Propogate( 'DoPonder', [ pcode ] );
                    this.MoveBy( deltas );
                }                
                break;
            
            case 'boundry':
                {
                    var amt = 2.7;
                    var dir = notMeName[1];
                    switch( dir )
                    {
                        case 'bottom':
                            this.world.ScrollTranslateBy(0,-amt);
                            break;
                        case 'top':
                            this.world.ScrollTranslateBy(0,amt);
                            break;
                        case 'left':
                            this.world.ScrollTranslateBy(amt,0);                            
                            break;
                        case 'right':
                            this.world.ScrollTranslateBy(-amt,0);
                            break;
                    }
                    
                }
                break;
            
            case 'mark':
                {
                    var markType = notMeName[1];
                    switch (markType)
                    {
                        case 'start':
                            {
                                var character = this.children.First();
                                character.DoPonder(kc.turnLC);
                                this.world.InitGame();
                                this.world.RemoveSprite(notMe);
                                notMe.Destroy();                                                    
                            }
                            break;
                    }
                    
                }
                break;
            
            case 'spouse':
                {
                    this.world.RemoveSprite( this.world.waitingSpouse );
                    this.world.waitingSpouse.Destroy();
                    this.world.waitingSpouse = null;
                    this.Add();
                    this.children.Propogate( 'DoPonder', [ kc.ponder ] );
                    this.TriggerHearts();
                }
                break;
        }
    },

    TriggerHearts: function()
    {
        this.heartsAnimation = new tooDeeLineFrameAnimation( {
                                    figureName: 'hearts',
                                    center: this.center,
                                    world: this.world,
                                    visible: true,
                                    showIfFrozen: false,
                                    scale: 44,
                                    zOrder: 100
                                });
        
        this.heartsAnimation.animator.Bind( 'cycle', this.KillHearts, this, 1 )
        
        this.world.AddSprite( this.heartsAnimation );
        
    },
    
    KillHearts: function()
    {
        var regKey;
        
        function doKillHearts()
        {
            this.world.RemoveSprite( this.heartsAnimation );
            this.heartsAnimation.Destroy();
            this.heartsAnimation = null;
            // er, do we need a 'preStepQueue' ?
            this.world.Unbind('preStep',regKey);
        }
        // this probably falls under the heading of 'hack' but
        // we need to keep this animation around for one more
        // round of 'Draw' so its shape doesn't linger on
        // the screen
        regKey = this.world.Bind('preStep',doKillHearts,this);
    },
    
    _getMaxCharacterBounds: function()
    {
        this.dimensions = new tooDeeDimensions();
        this.dimensions.Set( this.center.x, this.center.y, this.center.x, this.center.y );
        this.children.Propogate( 'GetBounds', [ this.dimensions ] );
    }
    
});


