
#include <stdlib.h>
#include <stdio.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <SFML/Graphics.hpp>
#include <Box2D/Box2D.h>

#include "utils.h"
#include "Log.h"
#include "Game.h"
#include "sound.h"
#include "Entity.h"

#include "ScreenLog.h"
#include "scripting.h"
#include "settings.h"
#include "b2dJson.h"

Game* g_game = NULL;

class MyDestructionListener : public b2DestructionListener
{
    void SayGoodbye(b2Joint* joint) {}
    void SayGoodbye(b2Fixture* fixture)
    {
        //this is not being hit, why?
        if ( fixture->GetUserData() ) {
            FixtureUserData* fud = (FixtureUserData*)fixture->GetUserData();
            if ( fud->getType() == FUD_KITTEN_FOOT_SENSOR ) {
                KittenFootSensor* kfs = (KittenFootSensor*)fud;
                delete kfs;//kfs contents will be taken care of in game class (m_kittens)
            }
        }
    }
};
MyDestructionListener myDestructionListener;

Game::Game()
{
    //loadSounds();
    loadImages();

    m_keyState = 0;
    m_mouseState = 0;
    m_currentMousePos = sf::Vector2i(-1,-1);

    m_started = false;
    m_paused = true;

    m_world = NULL;
    clearWorld();
}

Game::~Game()
{
    for ( int i = 0; i < (int)m_entities.size(); i++)
        delete m_entities[i];
    m_entities.clear();

    clearWorld();
    //clearSounds();
    clearImages();
}

void Game::clearWorld()
{
    g_screenLog->clear();

    DISCARD(m_world);
    m_playerUpper = NULL;
    m_playerLower = NULL;
    m_playerWheel = NULL;
    m_buddyUpper = NULL;
    m_buddyLower = NULL;
    m_buddyWheel = NULL;
    m_playerUpperFixture = NULL;
    m_playerJumpableFixture = NULL;
    m_buddyUpperFixture = NULL;
    m_buddyJumpableFixture = NULL;
    m_carLowerFixture = NULL;
    m_playerJumpableTimeout = 0;
    m_jumpableFixtureContactCount = 0;
    m_namedJointsMap.clear();
    m_namedFixturesMap.clear();
    m_scheduledEvents.clear();
    m_jointsToDeleteAfterStep.clear();
    m_fixturesToDeleteAfterStep.clear();

    m_playerSpeechBubble.m_expiryTime = 0;
    m_buddySpeechBubble.m_expiryTime = 0;

    for (size_t i = 0; i < m_kittens.size(); i++)
        delete m_kittens[i];
    m_kittens.clear();

    m_earthTriangles.clear();

    for (size_t i = 0; i < m_imageLocations0.size(); i++)
        delete m_imageLocations0[i];
    m_imageLocations0.clear();
    for (size_t i = 0; i < m_imageLocations1.size(); i++)
        delete m_imageLocations1[i];
    m_imageLocations1.clear();
    for (size_t i = 0; i < m_imageLocations2.size(); i++)
        delete m_imageLocations2[i];
    m_imageLocations2.clear();
}

bool Game::setup()
{
    g_game->setViewCenter( b2Vec2(-90,5) );
    g_game->setViewVerticalSpan(11.7f);

    m_started = false;
    setPause(true);
    clearWorld();

    b2dJson json;
    m_world = json.readFromFile("ld22.json");
    if ( !m_world ) {
        g_log.log(LL_FATAL, "Could not load ld22.json" );
        g_screenLog->addMessage("*** Could not load ld22.json", slErrorColor, 600);
        return false;
    }

    m_world->SetContactListener(this);
    m_debugDraw.SetFlags( b2Draw::e_shapeBit | b2Draw::e_jointBit );
    m_world->SetDebugDraw( &m_debugDraw );

    m_world->SetDestructionListener(&myDestructionListener);

#define FINDBODY( v, name )\
    v = json.getBodyByName(name);\
    if ( !v ) {\
        g_log.log(LL_FATAL, "Could not find \"%s\" body in ld22.json", name );\
        return false;\
    }

#define FINDJOINT( v, name )\
    v = (b2WheelJoint*)json.getJointByName(name);\
    if ( !v ) {\
        g_log.log(LL_FATAL, "Could not find \"%s\" joint in ld22.json", name );\
        return false;\
    }

#define FINDFIXTURE( v, name )\
    v = json.getFixtureByName(name);\
    if ( !v ) {\
        g_log.log(LL_FATAL, "Could not find \"%s\" fixture in ld22.json", name );\
        return false;\
    }

    FINDBODY(m_playerUpper, "playerUpper");
    FINDBODY(m_playerLower, "playerLower");
    FINDBODY(m_buddyUpper, "buddyUpper");
    FINDBODY(m_buddyLower, "buddyLower");

    FINDJOINT(m_playerWheel, "playerWheel");
    FINDJOINT(m_buddyWheel, "buddyWheel");

    FINDFIXTURE(m_playerUpperFixture, "playerUpper");
    FINDFIXTURE(m_playerJumpableFixture, "playerJumpable");
    FINDFIXTURE(m_buddyUpperFixture, "buddyUpper");
    FINDFIXTURE(m_buddyJumpableFixture, "buddyJumpable");

    /*m_playerUpper = json.getBodyByName("playerUpper");
    if ( !m_playerUpper ) {
        g_log.log(LL_FATAL, "Could not find \"playerUpper\" body in ld22.json" );
        g_screenLog->addMessage("*** Could not find \"playerUpper\" body in ld22.json", slErrorColor, 600);
        return false;
    }

    m_playerLower = json.getBodyByName("playerLower");
    if ( !m_playerLower ) {
        g_log.log(LL_FATAL, "Could not find \"playerLower\" body in ld22.json" );
        g_screenLog->addMessage("*** Could not find \"playerLower\" body in ld22.json", slErrorColor, 600);
        return false;
    }

    m_playerWheel = (b2WheelJoint*)json.getJointByName("playerWheel");
    if ( !m_playerWheel ) {
        g_log.log(LL_FATAL, "Could not find \"playerWheel\" joint in ld22.json" );
        g_screenLog->addMessage("*** Could not find \"playerWheel\" joint in ld22.json", slErrorColor, 600);
        return false;
    }

    m_playerUpperFixture = json.getFixtureByName("playerUpper");
    if ( !m_playerUpperFixture ) {
        g_log.log(LL_FATAL, "Could not find \"playerUpper\" fixture in ld22.json" );
        g_screenLog->addMessage("*** Could not find \"playerUpper\" fixture in ld22.json", slErrorColor, 600);
        return false;
    }

    m_playerJumpableFixture = json.getFixtureByName("playerJumpable");
    if ( !m_playerJumpableFixture ) {
        g_log.log(LL_FATAL, "Could not find \"playerJumpable\" fixture in ld22.json" );
        g_screenLog->addMessage("*** Could not find \"playerJumpable\" fixture in ld22.json", slErrorColor, 600);
        return false;
    }*/

    m_carLowerFixture = json.getFixtureByName("carLower");//doesn't really matter if this is not present

    loadEvents(json);
    loadNamedJoints(json);
    loadNamedFixtures(json);
    loadKittens(json);

    buildTriangles(json);
    loadImageLocations(json);

    m_cameraTrailPoint = m_playerUpper->GetPosition();

    m_playerWheel->SetMaxMotorTorque(g_playerRunStrength);
    m_playerWheel->SetMotorSpeed(0);
    m_playerWheel->EnableMotor(true);

    m_buddyWheel->SetMaxMotorTorque(g_playerRunStrength);
    m_buddyWheel->SetMotorSpeed(0);
    m_buddyWheel->EnableMotor(true);

    const int numEntities = 0;
    for ( int i = 0; i < numEntities; i++) {
        Entity* entity = new Entity();
        entity->script_init();
        m_entities.push_back(entity);

        char buf[32];
        sprintf(buf, "Entity%d", i);
        entity->setName(buf);
    }

    //playSound(SID_ITWORKS);

    g_screenLog->addMessage(
                "Welcome to 'Alone in the park', Ludum Dare 22 entry from iforce2d\n\n"
                "Keys:\n  left = A, Q or left arrow\n  right = D or right arrow\n  jump = space\n\n  reset the game = R\n  pause = P\n\n"
                "Check out the settings.as file to change display settings\nThere is no sound, sorry!\n\n"
                , slEventColor, 6000);
    g_screenLog->addMessage(
                "\n\n\n\n\n\n\n\n\n\n\n\n"
                "One day you are going hiking with your buddy...\n\n (press space to start)"
                , slNarrativeColor, 6000);

    return true;
}

void Game::setPause(bool p)
{
    m_paused = p;
    if (!p) {
        if ( !m_started )
            g_screenLog->clear();
        m_started = true;
    }
}

void Game::togglePause()
{
    setPause( !m_paused );
}

void Game::loadImages()
{
    if ( ! m_earthImage.LoadFromFile("earth.png") )
        g_log.log(LL_ERROR, "Could not load %s", "earth.png");
    if ( ! m_woodImage.LoadFromFile("wood.png") )
        g_log.log(LL_ERROR, "Could not load %s", "wood.png");
    if ( ! m_stoneImage.LoadFromFile("stone.png") )
        g_log.log(LL_ERROR, "Could not load %s", "stone.png");
}

void Game::clearImages()
{

}

void Game::loadEvents(b2dJson& json)
{
    int i = 0;
    int loaded = 0;
    while (i < 100) {
        char buf[32];
        sprintf(buf, "event%d", i);
        b2Fixture* fixture = json.getFixtureByName(buf);
        if ( fixture ) {
            EventSensor* es = new EventSensor();
            es->id = i;
            fixture->SetUserData(es);
            loaded++;
            g_log.log(LL_INFO, "Loaded event %d", i);
        }
        i++;
    }
    g_log.log(LL_INFO, "Loaded %d event sensors", loaded);
}

void Game::loadNamedJoints(b2dJson& json)
{
    m_namedJointsMap.clear();
    map<b2Joint*,string> jtnm = json.getJointToNameMap();
    map<b2Joint*,string>::iterator it = jtnm.begin();
    map<b2Joint*,string>::iterator end = jtnm.end();
    while (it != end) {
        m_namedJointsMap[it->second] = it->first;
        ++it;
    }
}

void Game::loadNamedFixtures(b2dJson& json)
{
    m_namedFixturesMap.clear();
    map<b2Fixture*,string> ftnm = json.getFixtureToNameMap();
    map<b2Fixture*,string>::iterator it = ftnm.begin();
    map<b2Fixture*,string>::iterator end = ftnm.end();
    while (it != end) {
        m_namedFixturesMap[it->second] = it->first;
        ++it;
    }
}

void Game::loadKittens(b2dJson& json)
{
    vector<b2Fixture*> kittenFootFixtures;
    json.getFixturesByName("kittenFoot", kittenFootFixtures);
    for (size_t i = 0; i < kittenFootFixtures.size(); i++) {
        b2Fixture* fixture = kittenFootFixtures[i];
        KittenFootSensor* kfs = new KittenFootSensor();
        kfs->m_kitten = new kitten;
        kfs->m_kitten->m_body = fixture->GetBody();
        m_kittens.push_back(kfs->m_kitten);
        fixture->SetUserData(kfs);
    }
}

b2Vec2 getVertexFromChainShape(b2ChainShape* chainShape, int index)
{
    int numChildEdges = chainShape->GetChildCount();
    b2Assert( index <= numChildEdges );
    b2EdgeShape edgeShape;
    chainShape->GetChildEdge( &edgeShape, b2Min(index,numChildEdges-1) );
    if ( index < numChildEdges )
        return edgeShape.m_vertex1;
    else
        return edgeShape.m_vertex2;
}

void Game::loadImageLocations(b2dJson& json)
{
    map<b2Fixture*,string> ftnm = json.getFixtureToNameMap();
    map<b2Fixture*,string>::iterator it = ftnm.begin();
    map<b2Fixture*,string>::iterator end = ftnm.end();
    while (it != end) {

        if ( it->second.find("img0_") == 0 ) {

            b2Shape* shape = it->first->GetShape();
            b2PolygonShape* polygonShape = (b2PolygonShape*)shape;
            if ( polygonShape->GetVertexCount() == 4 ) {
                imageLocation* il = new imageLocation;
                for (int c = 0; c < 4; c++)
                    il->m_points[c] = polygonShape->GetVertex(c);
                string imgFile = it->second.substr(5) + string(".png");
                il->m_texture.LoadFromFile(imgFile);
                il->m_body = it->first->GetBody();
                m_imageLocations0.push_back(il);
            }
        }
        else if ( it->second.find("img1_") == 0 ) {

            b2Shape* shape = it->first->GetShape();
            b2PolygonShape* polygonShape = (b2PolygonShape*)shape;
            if ( polygonShape->GetVertexCount() == 4 ) {
                imageLocation* il = new imageLocation;
                for (int c = 0; c < 4; c++)
                    il->m_points[c] = polygonShape->GetVertex(c);
                string imgFile = it->second.substr(5) + string(".png");
                il->m_texture.LoadFromFile(imgFile);
                il->m_body = it->first->GetBody();
                m_imageLocations1.push_back(il);
            }
        }
        else if ( it->second.find("img2_") == 0 ) {

            b2Shape* shape = it->first->GetShape();
            b2PolygonShape* polygonShape = (b2PolygonShape*)shape;
            if ( polygonShape->GetVertexCount() == 4 ) {
                imageLocation* il = new imageLocation;
                for (int c = 0; c < 4; c++)
                    il->m_points[c] = polygonShape->GetVertex(c);
                string imgFile = it->second.substr(5) + string(".png");
                il->m_texture.LoadFromFile(imgFile);
                il->m_body = it->first->GetBody();
                m_imageLocations2.push_back(il);
            }
        }

        ++it;
    }
}

void Game::buildTriangles(b2dJson& json)
{
    {
        vector<b2Fixture*> earthLoops;
        json.getFixturesByName("earth", earthLoops);

        for (size_t i = 0; i < earthLoops.size(); i++) {
            b2Fixture* fixture = earthLoops[i];
            b2Shape* shape = fixture->GetShape();
            b2ChainShape* chainShape = (b2ChainShape*)shape;
            vector<p2t::Point*> polyline;
            for (int c = 0; c < shape->GetChildCount(); c++) {
                b2Vec2 v = getVertexFromChainShape(chainShape, c);
                polyline.push_back( new p2t::Point(v.x, v.y) );
            }

            p2t::CDT cdt(polyline);
            cdt.Triangulate();
            vector<p2t::Triangle*> tris = cdt.GetTriangles();
            for (size_t t = 0; t < tris.size(); t++) {
                p2t::Triangle* tri = tris[t];
                myTriangle mytri;
                for (int ind = 0; ind < 3; ind++) {
                    mytri.m_points[ind].x = tri->GetPoint(ind)->x;
                    mytri.m_points[ind].y = tri->GetPoint(ind)->y;
                }
                mytri.m_body = fixture->GetBody();
                m_earthTriangles.push_back(mytri);
            }
        }
    }

    {
        vector<b2Fixture*> woodPolys;
        json.getFixturesByName("wood", woodPolys);

        for (size_t i = 0; i < woodPolys.size(); i++) {
            b2Fixture* fixture = woodPolys[i];
            b2Shape* shape = fixture->GetShape();
            b2PolygonShape* polyShape = (b2PolygonShape*)shape;
            vector<p2t::Point*> polyline;
            for (int c = 0; c < polyShape->GetVertexCount(); c++) {
                b2Vec2 v = polyShape->GetVertex(c);
                polyline.push_back( new p2t::Point(v.x, v.y) );
            }

            p2t::CDT cdt(polyline);
            cdt.Triangulate();
            vector<p2t::Triangle*> tris = cdt.GetTriangles();
            for (size_t t = 0; t < tris.size(); t++) {
                p2t::Triangle* tri = tris[t];
                myTriangle mytri;
                for (int ind = 0; ind < 3; ind++) {
                    mytri.m_points[ind].x = tri->GetPoint(ind)->x;
                    mytri.m_points[ind].y = tri->GetPoint(ind)->y;
                }
                mytri.m_body = fixture->GetBody();
                m_woodTriangles.push_back(mytri);
            }
        }
    }

    {
        vector<b2Fixture*> stonePolys;
        json.getFixturesByName("stone", stonePolys);

        for (size_t i = 0; i < stonePolys.size(); i++) {
            b2Fixture* fixture = stonePolys[i];
            b2Shape* shape = fixture->GetShape();
            if ( shape->GetType() != b2Shape::e_polygon )
                continue;
            b2PolygonShape* polyShape = (b2PolygonShape*)shape;
            vector<p2t::Point*> polyline;
            for (int c = 0; c < polyShape->GetVertexCount(); c++) {
                b2Vec2 v = polyShape->GetVertex(c);
                polyline.push_back( new p2t::Point(v.x, v.y) );
            }

            p2t::CDT cdt(polyline);
            cdt.Triangulate();
            vector<p2t::Triangle*> tris = cdt.GetTriangles();
            for (size_t t = 0; t < tris.size(); t++) {
                p2t::Triangle* tri = tris[t];
                myTriangle mytri;
                for (int ind = 0; ind < 3; ind++) {
                    mytri.m_points[ind].x = tri->GetPoint(ind)->x;
                    mytri.m_points[ind].y = tri->GetPoint(ind)->y;
                }
                mytri.m_body = fixture->GetBody();
                m_stoneTriangles.push_back(mytri);
            }
        }
    }
}

void Game::destroyJoint(string name)
{
    map<string, b2Joint*>::iterator it = m_namedJointsMap.find( name );
    if ( it == m_namedJointsMap.end() )
        return;

    b2Joint* j = it->second;
    m_jointsToDeleteAfterStep.push_back(j);
}

void Game::destroyFixture(string name)
{
    map<string, b2Fixture*>::iterator it = m_namedFixturesMap.find( name );
    if ( it == m_namedFixturesMap.end() )
        return;

    b2Fixture* f = it->second;
    m_fixturesToDeleteAfterStep.push_back(f);
}

void Game::clearReferencesToJoint(b2Joint* j)
{
    map<string, b2Joint*>::iterator it = m_namedJointsMap.begin();
    map<string, b2Joint*>::iterator end = m_namedJointsMap.end();
    while (it != end) {
        if ( it->second == j ) {
            map<string, b2Joint*>::iterator toErase = it;
            ++it;
            m_namedJointsMap.erase(toErase);
            continue;
        }
        ++it;
    }
}

void Game::clearReferencesToFixture(b2Fixture* f)
{
    map<string, b2Fixture*>::iterator it = m_namedFixturesMap.begin();
    map<string, b2Fixture*>::iterator end = m_namedFixturesMap.end();
    while (it != end) {
        if ( it->second == f ) {
            map<string, b2Fixture*>::iterator toErase = it;
            ++it;
            m_namedFixturesMap.erase(toErase);
            continue;
        }
        ++it;
    }
}

void Game::scheduleEvent(int eventId, float timeAheadSeconds)
{
    scheduledEvent se;
    se.m_eventId = eventId;
    se.m_scheduledTime = m_clock.GetElapsedTime() + timeAheadSeconds * 1000;
    m_scheduledEvents.push_back(se);
}

void Game::setSpeech(int actorId, string msg, float timeoutSeconds)
{
    if ( actorId == EA_PLAYER ) {
        m_playerSpeechBubble.m_message = msg;
        m_playerSpeechBubble.m_expiryTime = m_clock.GetElapsedTime() + timeoutSeconds * 1000;
    }
    else if ( actorId == EA_BUDDY ) {
        m_buddySpeechBubble.m_message = msg;
        m_buddySpeechBubble.m_expiryTime = m_clock.GetElapsedTime() + timeoutSeconds * 1000;
    }
}

void Game::step()
{
    if (! m_world)
        return;

    float timenow = m_clock.GetElapsedTime();

    if ( g_cameraMode == CM_FOLLOW_PLAYER ) {

        float maxCameraLead = m_viewVerticalSpan * 0.175f;
        b2Vec2 playerPosNow = m_playerUpper->GetPosition();
        b2Vec2 toTrailPoint = m_cameraTrailPoint - playerPosNow;
        if ( toTrailPoint.Length() > maxCameraLead ) {
            toTrailPoint.Normalize();
            toTrailPoint *= maxCameraLead;

        }m_cameraTrailPoint = playerPosNow + toTrailPoint;
        float xUptake = 0.025f;//smooth this movement
        float yUptake = 0.0125f;
        m_viewCenter.x = (1-xUptake) * m_viewCenter.x + xUptake * (playerPosNow.x - toTrailPoint.x);
        m_viewCenter.y = (1-yUptake) * m_viewCenter.y + yUptake * (playerPosNow.y - toTrailPoint.y);

        float cameraBound = 0.25f * m_viewVerticalSpan;
        if ( m_viewCenter.x < playerPosNow.x - cameraBound )
            m_viewCenter.x = playerPosNow.x - cameraBound;
        else if ( m_viewCenter.x > playerPosNow.x + cameraBound )
            m_viewCenter.x = playerPosNow.x + cameraBound;
        if ( m_viewCenter.y < playerPosNow.y - cameraBound )
            m_viewCenter.y = playerPosNow.y - cameraBound;
        else if ( m_viewCenter.y > playerPosNow.y + cameraBound )
            m_viewCenter.y = playerPosNow.y + cameraBound;
    }

    if ( m_paused )
        return;

    //player movement - should really be in a separate class
    if ( m_playerWheel ) {
        float currentSpeed = m_playerWheel->GetJointSpeed();
        float desiredSpeed = 0;
        b2Vec2 desiredAirSteerForce(0,0);

        int horizontal = m_keyState & (KS_LEFT|KS_RIGHT);
        float runUptake = 0.1f;//dont try to immediately set rotation to max from standstill, helps with friction problems
        if ( horizontal == KS_LEFT ) {
            desiredSpeed = currentSpeed * (1-runUptake) + g_playerRunSpeed * runUptake;
            desiredAirSteerForce.x = -g_playerAirSteerStrength;
        }
        else if ( horizontal == KS_RIGHT ) {
            desiredSpeed = currentSpeed * (1-runUptake) - g_playerRunSpeed * runUptake;
            desiredAirSteerForce.x = g_playerAirSteerStrength;
        }

        m_playerWheel->SetMotorSpeed(desiredSpeed);

        float currentHorizontalVelocity = m_playerUpper->GetLinearVelocity().x;
        if ( fabsf(currentHorizontalVelocity) < 2 ) {
            m_playerUpper->ApplyForce( desiredAirSteerForce, m_playerUpper->GetWorldCenter() );
            m_playerLower->ApplyForce( desiredAirSteerForce, m_playerLower->GetWorldCenter() );
        }

        int vertical = m_keyState & (KS_UP|KS_DOWN|KS_JUMP);
        if ( vertical == KS_JUMP && m_jumpableFixtureContactCount > 0 ) {
            if ( timenow > m_playerJumpableTimeout ) {
                float currentVerticalSpeed = 0.5f * (m_playerUpper->GetLinearVelocity().y + m_playerLower->GetLinearVelocity().y);
                float desiredJumpSpeed = g_playerJumpStrength;
                b2Vec2 impulse(0, desiredJumpSpeed - currentVerticalSpeed);
                if ( impulse.y > 0 ) {
                    m_playerUpper->ApplyLinearImpulse( impulse, m_playerUpper->GetWorldCenter() );
                    m_playerLower->ApplyLinearImpulse( impulse, m_playerLower->GetWorldCenter() );
                    m_playerJumpableTimeout = timenow + 500;
                }
            }
        }
    }

    //buddy movement - should really be in a separate class
    if ( m_buddyWheel ) {
        float currentSpeed = m_buddyWheel->GetJointSpeed();
        float desiredSpeed = 0;
        b2Vec2 desiredAirSteerForce(0,0);

        b2Vec2 playerPos = m_playerUpper->GetPosition();
        b2Vec2 buddyPos = m_buddyUpper->GetPosition();
        float runUptake = 0.1f;//dont try to immediately set rotation to max from standstill, helps with friction problems
        if ( (playerPos - buddyPos).Length() < 3.5 ) {
            desiredSpeed = currentSpeed * (1-runUptake) - g_playerRunSpeed * runUptake;
            desiredAirSteerForce.x = g_playerAirSteerStrength;
        }
        else {
            desiredSpeed = currentSpeed * 0.95f;
        }

        m_buddyWheel->SetMotorSpeed(desiredSpeed);
        float currentHorizontalVelocity = m_playerUpper->GetLinearVelocity().x;
        if ( fabsf(currentHorizontalVelocity) < 2 ) {
            m_buddyUpper->ApplyForce( desiredAirSteerForce, m_buddyUpper->GetWorldCenter() );
            m_buddyLower->ApplyForce( desiredAirSteerForce, m_buddyLower->GetWorldCenter() );
        }
    }

    m_world->Step(1/60.0f, 8, 3);

    //destroy scheduled fixtures
    for (size_t i = 0; i < m_fixturesToDeleteAfterStep.size(); i++) {
        b2Fixture* fixture = m_fixturesToDeleteAfterStep[i];
        fixture->GetBody()->DestroyFixture(fixture);
        clearReferencesToFixture(fixture);
    }
    m_fixturesToDeleteAfterStep.clear();

    //destroy scheduled joints
    for (size_t i = 0; i < m_jointsToDeleteAfterStep.size(); i++) {
        b2Joint* joint = m_jointsToDeleteAfterStep[i];
        m_world->DestroyJoint(joint);
        clearReferencesToJoint(joint);
    }
    m_jointsToDeleteAfterStep.clear();

    //run scheduled events
    for (size_t i = 0; i < m_scheduledEvents.size(); i++) {
        scheduledEvent& se = m_scheduledEvents[i];
        if ( timenow > se.m_scheduledTime ) {
            runEvent(-1, se.m_eventId);//may add new events to end of this vector
            m_scheduledEvents.erase( m_scheduledEvents.begin() + i );
            i--;
        }
    }

    //for ( int i = 0; i < (int)m_entities.size(); i++)
    //    m_entities[i]->step();

    b2Vec2 playerPos = m_playerUpper->GetPosition();
    for ( size_t i = 0; i < m_kittens.size(); i++) {
        kitten* k = m_kittens[i];
        if ( k->numFeetOnGround > 0 && timenow > k->nextJumpTime ) {
            b2Vec2 impulse(0,0.03f);
            if ( playerPos.x < k->m_body->GetPosition().x )
                impulse.x = -0.035f;
            else
                impulse.x = 0.035f;
            k->m_body->ApplyLinearImpulse( impulse, k->m_body->GetWorldCenter() );
            k->nextJumpTime = timenow + 600 + 400 * rnd();//600 - 1000ms
        }
    }
}

void Game::applyView(sf::RenderWindow& window)
{
    float aspect = window.GetWidth() / (float)window.GetHeight();
    float viewHorizontalSpan = m_viewVerticalSpan * aspect;
    float lx = m_viewCenter.x - 0.5f * viewHorizontalSpan;
    float ux = m_viewCenter.x + 0.5f * viewHorizontalSpan;
    float ly = m_viewCenter.y - 0.5f * m_viewVerticalSpan;
    float uy = m_viewCenter.y + 0.5f * m_viewVerticalSpan;

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D( lx, ux, ly, uy );

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

b2Vec2 Game::pixelToWorld(sf::RenderWindow& window, sf::Vector2f pixelLocation)
{
    float ww = window.GetWidth();
    float wh = window.GetHeight();
    float aspect = ww / (float)wh;
    float viewHorizontalSpan = m_viewVerticalSpan * aspect;

    float xf = pixelLocation.x / (float)ww;
    float yf = (wh - pixelLocation.y) / (float)wh;

    float lx = m_viewCenter.x - 0.5f * viewHorizontalSpan;
    float ly = m_viewCenter.y - 0.5f * m_viewVerticalSpan;
    return b2Vec2( lx + xf * viewHorizontalSpan, ly + yf * m_viewVerticalSpan );
}

sf::Vector2f Game::worldToPixel(sf::RenderWindow& window, b2Vec2 worldLocation)
{
    float ww = window.GetWidth();
    float wh = window.GetHeight();
    float aspect = ww / (float)wh;
    float viewHorizontalSpan = m_viewVerticalSpan * aspect;

    float lx = m_viewCenter.x - 0.5f * viewHorizontalSpan;
    float ly = m_viewCenter.y - 0.5f * m_viewVerticalSpan;

    float xf = (worldLocation.x - lx) / viewHorizontalSpan;
    float yf = (worldLocation.y - ly) / m_viewVerticalSpan;
    return sf::Vector2f( xf * ww, wh - yf * wh );
}

b2Vec2 Game::pixelToWorld_vector(sf::RenderWindow& window, sf::Vector2f pixelVector)
{
    float ratio = m_viewVerticalSpan / window.GetHeight();
    return ratio * b2Vec2(pixelVector.x, pixelVector.y);
}

void Game::keyDown(sf::RenderWindow& window, sf::Event::KeyEvent keyEvent)
{
    //trying to support different keyboard layouts as suggested here
    //http://www.ludumdare.com/compo/2011/12/13/dos-and-donts-for-setting-up-your-game/
    //you guys better be right!!
    //  W,A,S,D = up,left,down,right
    //  Z,Q,S,D = up,left,down,right
    //  , A O E = up left down right
    switch ( keyEvent.Code )
    {
    case sf::Keyboard::Left:
    case sf::Keyboard::A:
    case sf::Keyboard::Q:
        m_keyState |= KS_LEFT;
        break;
    case sf::Keyboard::Right:
    case sf::Keyboard::D:
    case sf::Keyboard::E:
        m_keyState |= KS_RIGHT;
        break;
    case sf::Keyboard::Up:
    case sf::Keyboard::W:
    case sf::Keyboard::Z:
    case sf::Keyboard::Comma:
        m_keyState |= KS_UP;
        break;
    case sf::Keyboard::Down:
    case sf::Keyboard::S:
    case sf::Keyboard::O:
        m_keyState |= KS_DOWN;
        break;
    case sf::Keyboard::Space:
        m_keyState |= KS_JUMP;
        break;
    default:
        break;
    }
}

void Game::keyUp(sf::RenderWindow& window, sf::Event::KeyEvent keyEvent)
{
    switch ( keyEvent.Code )
    {
    case sf::Keyboard::Left:
    case sf::Keyboard::A:
    case sf::Keyboard::Q:
        m_keyState &= ~KS_LEFT;
        break;
    case sf::Keyboard::Right:
    case sf::Keyboard::D:
    case sf::Keyboard::E:
        m_keyState &= ~KS_RIGHT;
        break;
    case sf::Keyboard::Up:
    case sf::Keyboard::W:
    case sf::Keyboard::Z:
    case sf::Keyboard::Comma:
        m_keyState &= ~KS_UP;
        break;
    case sf::Keyboard::Down:
    case sf::Keyboard::S:
    case sf::Keyboard::O:
        m_keyState &= ~KS_DOWN;
        break;
    case sf::Keyboard::Space:
        m_keyState &= ~KS_JUMP;
        break;
    default:
        break;
    }
}

void Game::mouseDown(sf::RenderWindow& window, sf::Event::MouseButtonEvent mouseButtonEvent)
{
    //playSound(SID_ITWORKS2);

    if ( mouseButtonEvent.Button == sf::Mouse::Left )
        m_mouseState |= MS_LEFT;
    else if ( mouseButtonEvent.Button == sf::Mouse::Right )
        m_mouseState |= MS_RIGHT;

    //g_screenLog->addMessage("You clicked the mouse", slEventColor, 6);
}

void Game::mouseUp(sf::RenderWindow& window, sf::Event::MouseButtonEvent mouseButtonEvent)
{
    if ( mouseButtonEvent.Button == sf::Mouse::Left )
        m_mouseState &= ~MS_LEFT;
    else if ( mouseButtonEvent.Button == sf::Mouse::Right )
        m_mouseState &= ~MS_RIGHT;

    //g_screenLog->addMessage("test \"help! I'm being eaten by kittens!!!\"", slSpeechColor, 2);
}

void Game::mouseMove(sf::RenderWindow& window, sf::Event::MouseMoveEvent mouseMoveEvent)
{
    if ( g_cameraMode == CM_PAN_WITH_MOUSE ) {
        if ( m_currentMousePos.x != -1 ) {//ignore first mouse movement
            if ( m_mouseState & MS_RIGHT ) {
                int mx = mouseMoveEvent.X - m_currentMousePos.x;
                int my = mouseMoveEvent.Y - m_currentMousePos.y;
                b2Vec2 worldMoved = pixelToWorld_vector(window, sf::Vector2f(-mx,my));
                m_viewCenter += worldMoved;
            }
        }
    }

    m_currentMousePos.x = mouseMoveEvent.X;
    m_currentMousePos.y = mouseMoveEvent.Y;
}

void Game::mouseWheel(sf::RenderWindow& window, sf::Event::MouseWheelEvent mouseWheelEvent)
{
    if ( g_cameraMode == CM_PAN_WITH_MOUSE ) {
        float zoomMod = 1 - mouseWheelEvent.Delta * 0.1f;
        m_viewVerticalSpan *= zoomMod;
        m_viewVerticalSpan = b2Clamp(m_viewVerticalSpan, 5.0f, 50.0f);
    }
}

void Game::renderWorld(sf::RenderWindow& window)
{
    applyView(window);

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    float tgs[4] = {0.25f,0,0,0};
    float tgt[4] = {0,0.25f,0,0};

    //draw earth
    glColor3f(1,1,1);
    glEnable(GL_TEXTURE_2D);
    glEnableClientState(GL_VERTEX_ARRAY);
    //glPointSize(10);
    //glPolygonMode(GL_FRONT, GL_LINE);

    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    //draw 'sprites'
    float texCoords[8] = {1,1,  1,0,  0,0,  0,1};
    for (size_t i = 0; i < m_imageLocations0.size(); i++) {
        imageLocation* il = m_imageLocations0[i];
        il->m_texture.Bind();
        glTexCoordPointer(2, GL_FLOAT, 0, texCoords);
        glVertexPointer(2, GL_FLOAT, 0, il->m_points);
        glDrawArrays(GL_QUADS, 0, 4);
    }
    for (size_t i = 0; i < m_imageLocations1.size(); i++) {
        imageLocation* il = m_imageLocations1[i];
        il->m_texture.Bind();
        glTexCoordPointer(2, GL_FLOAT, 0, texCoords);
        glVertexPointer(2, GL_FLOAT, 0, il->m_points);
        glPushMatrix();
        b2Vec2 pos = il->m_body->GetPosition();
        glTranslatef( pos.x, pos.y, 0 );
        glRotatef( il->m_body->GetAngle() * RADTODEG, 0, 0, 1 );
        glDrawArrays(GL_QUADS, 0, 4);
        glPopMatrix();
    }
    glDisableClientState(GL_TEXTURE_COORD_ARRAY);

    //draw earth, wood, stone
    //glEnable(GL_TEXTURE_GEN_S);
    //glEnable(GL_TEXTURE_GEN_T);
    //glTexGenf(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
    //glTexGenf(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
    //glTexGenfv(GL_S, GL_OBJECT_PLANE, tgs);
    //glTexGenfv(GL_T, GL_OBJECT_PLANE, tgt);
    //glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT );
    //glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT );

    m_stoneImage.Bind();
    for (size_t i = 0; i < m_stoneTriangles.size(); i++) {
        myTriangle& tri = m_stoneTriangles[i];
        glVertexPointer(2, GL_FLOAT, 0, tri.m_points);
        glPushMatrix();
        b2Vec2 pos = tri.m_body->GetPosition();
        glTranslatef( pos.x, pos.y, 0 );
        glRotatef( tri.m_body->GetAngle() * RADTODEG, 0, 0, 1 );
        glDrawArrays(GL_TRIANGLES, 0, 3);
        glPopMatrix();
    }
    m_earthImage.Bind();
    for (size_t i = 0; i < m_earthTriangles.size(); i++) {
        myTriangle& tri = m_earthTriangles[i];
        glVertexPointer(2, GL_FLOAT, 0, tri.m_points);
        glDrawArrays(GL_TRIANGLES, 0, 3);
    }
    m_woodImage.Bind();
    for (size_t i = 0; i < m_woodTriangles.size(); i++) {
        myTriangle& tri = m_woodTriangles[i];
        glVertexPointer(2, GL_FLOAT, 0, tri.m_points);
        glPushMatrix();
        b2Vec2 pos = tri.m_body->GetPosition();
        glTranslatef( pos.x, pos.y, 0 );
        glRotatef( tri.m_body->GetAngle() * RADTODEG, 0, 0, 1 );
        glDrawArrays(GL_TRIANGLES, 0, 3);
        glPopMatrix();
    }
    //glDisable(GL_TEXTURE_GEN_S);
    //glDisable(GL_TEXTURE_GEN_T);

    //draw important dynamic objects
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    for (size_t i = 0; i < m_imageLocations2.size(); i++) {
        imageLocation* il = m_imageLocations2[i];
        il->m_texture.Bind();
        glTexCoordPointer(2, GL_FLOAT, 0, texCoords);
        glVertexPointer(2, GL_FLOAT, 0, il->m_points);

        glPushMatrix();
        b2Vec2 pos = il->m_body->GetPosition();
        glTranslatef( pos.x, pos.y, 0 );
        glRotatef( il->m_body->GetAngle() * RADTODEG, 0, 0, 1 );
        glDrawArrays(GL_QUADS, 0, 4);
        glPopMatrix();
    }
    glDisableClientState(GL_TEXTURE_COORD_ARRAY);

    glDisable(GL_TEXTURE_2D);
    glDisableClientState(GL_VERTEX_ARRAY);

    /*glColor3f(1,1,1);
    glBegin(GL_POINTS);
    glVertex2f( m_cameraTrailPoint.x, m_cameraTrailPoint.y );
    glEnd();*/

    /*glColor3f(1,1,1);
    glBegin(GL_LINE_LOOP);
    glVertex2f( 0, 0);
    glVertex2f(10, 0);
    glVertex2f(10,10);
    glVertex2f( 0,10);
    glEnd();

    glPointSize(5);
    glEnable(GL_POINT_SMOOTH);
    glBegin(GL_POINTS);
    for ( int i = 0; i < (int)m_entities.size(); i++) {
        Entity* entity = m_entities[i];
        glColor3f(entity->m_color.r, entity->m_color.g, entity->m_color.b);
        glVertex2f(entity->m_pos.x, entity->m_pos.y);
    }
    glEnd();*/

    if ( g_doDebugDraw )
        m_world->DrawDebugData();
}

void Game::renderOverlay(sf::RenderWindow& window, sf::Text &sfText)
{
    sfText.SetCharacterSize(16);
    float timenow = m_clock.GetElapsedTime();
    if ( m_playerSpeechBubble.m_expiryTime > timenow ) {
        b2Vec2 speechBubbleOffset(0.35f, 1);

        sf::Color color = slSpeechColor;
        if ( m_playerSpeechBubble.m_expiryTime - timenow < 1000 )
            color.a = (m_playerSpeechBubble.m_expiryTime - timenow) * 0.001f * 255;
        sfText.SetColor( color );

        sfText.SetString(m_playerSpeechBubble.m_message);
        sf::Vector2f screenPos = worldToPixel(window, m_playerUpper->GetPosition() + speechBubbleOffset );
        int numChars = m_playerSpeechBubble.m_message.length();
        screenPos.x -= numChars / 2 * sfText.GetCharacterSize() * 0.5f;
        sfText.SetPosition( screenPos );
        window.Draw(sfText);
    }
    if ( m_buddySpeechBubble.m_expiryTime > timenow ) {
        b2Vec2 speechBubbleOffset(0.35f, 1.5f);//try to keep it off the other guys speech

        sf::Color color = slSpeechColor;
        if ( m_buddySpeechBubble.m_expiryTime - timenow < 1000 )
            color.a = (m_buddySpeechBubble.m_expiryTime - timenow) * 0.001f * 255;
        sfText.SetColor( color );

        sfText.SetString(m_buddySpeechBubble.m_message);
        sf::Vector2f screenPos = worldToPixel(window, m_buddyUpper->GetPosition() + speechBubbleOffset );
        int numChars = m_buddySpeechBubble.m_message.length();
        screenPos.x -= numChars / 2 * sfText.GetCharacterSize() * 0.5f;
        sfText.SetPosition( screenPos );
        window.Draw(sfText);
    }

    /*for ( int i = 0; i < (int)m_entities.size(); i++) {
        Entity* entity = m_entities[i];
        sfText.SetString(entity->getTostring());
        sfText.SetPosition( worldToPixel(window, entity->getPos()) );
        window.Draw(sfText);
    }*/
}

void Game::BeginContact(b2Contact* contact) {

    b2Fixture* fixtureA = contact->GetFixtureA();
    b2Fixture* fixtureB = contact->GetFixtureB();

    checkEventSensor(fixtureA, fixtureB);

    if ( (fixtureA == m_playerJumpableFixture && !fixtureB->IsSensor()) || (fixtureB == m_playerJumpableFixture && !fixtureA->IsSensor()) ) {
        m_jumpableFixtureContactCount++;
        if ( m_jumpableFixtureContactCount == 1 )//just touched down, shouldn't jump again instantaneously
            m_playerJumpableTimeout = m_clock.GetElapsedTime() + 150;
    }

    //kitten jumpable check
    if ( fixtureA->GetUserData() ) {
        FixtureUserData* fud = (FixtureUserData*)fixtureA->GetUserData();
        if ( fud->getType() == FUD_KITTEN_FOOT_SENSOR ) {
            KittenFootSensor* kfs = (KittenFootSensor*)fud;
            kfs->m_kitten->numFeetOnGround++;
        }
    }
    else if ( fixtureB->GetUserData() ) {
        FixtureUserData* fud = (FixtureUserData*)fixtureB->GetUserData();
        if ( fud->getType() == FUD_KITTEN_FOOT_SENSOR ) {
            KittenFootSensor* kfs = (KittenFootSensor*)fud;
            kfs->m_kitten->numFeetOnGround++;
        }
    }

}

void Game::EndContact(b2Contact* contact) {

    b2Fixture* fixtureA = contact->GetFixtureA();
    b2Fixture* fixtureB = contact->GetFixtureB();

    if ( (fixtureA == m_playerJumpableFixture && !fixtureB->IsSensor()) || (fixtureB == m_playerJumpableFixture && !fixtureA->IsSensor()) )
        m_jumpableFixtureContactCount--;

    //kitten jumpable check
    if ( fixtureA->GetUserData() ) {
        FixtureUserData* fud = (FixtureUserData*)fixtureA->GetUserData();
        if ( fud->getType() == FUD_KITTEN_FOOT_SENSOR ) {
            KittenFootSensor* kfs = (KittenFootSensor*)fud;
            kfs->m_kitten->numFeetOnGround--;
        }
    }
    else if ( fixtureB->GetUserData() ) {
        FixtureUserData* fud = (FixtureUserData*)fixtureB->GetUserData();
        if ( fud->getType() == FUD_KITTEN_FOOT_SENSOR ) {
            KittenFootSensor* kfs = (KittenFootSensor*)fud;
            kfs->m_kitten->numFeetOnGround--;
        }
    }

}

void Game::checkEventSensor( b2Fixture* fixtureA, b2Fixture* fixtureB )
{
    b2Fixture* eventSensorFixture = NULL;
    int actorId = -1;
    int eventSensorId = -1;

    //player
    if ( fixtureA == m_playerUpperFixture && fixtureB->GetUserData() ) {
        FixtureUserData* fud = (FixtureUserData*)fixtureB->GetUserData();
        if ( fud->getType() == FUD_EVENT_SENSOR ) {
            EventSensor* es = (EventSensor*)fud;
            eventSensorId = es->id;
            actorId = EA_PLAYER;
            eventSensorFixture = fixtureB;
        }
    }
    else if ( fixtureB == m_playerUpperFixture && fixtureA->GetUserData() ) {
        FixtureUserData* fud = (FixtureUserData*)fixtureA->GetUserData();
        if ( fud->getType() == FUD_EVENT_SENSOR ) {
            EventSensor* es = (EventSensor*)fud;
            eventSensorId = es->id;
            actorId = EA_PLAYER;
            eventSensorFixture = fixtureA;
        }
    }

    //buddy
    else if ( fixtureA == m_buddyUpperFixture && fixtureB->GetUserData() ) {
        FixtureUserData* fud = (FixtureUserData*)fixtureB->GetUserData();
        if ( fud->getType() == FUD_EVENT_SENSOR ) {
            EventSensor* es = (EventSensor*)fud;
            eventSensorId = es->id;
            actorId = EA_BUDDY;
            eventSensorFixture = fixtureB;
        }
    }
    else if ( fixtureB == m_buddyUpperFixture && fixtureA->GetUserData() ) {
        FixtureUserData* fud = (FixtureUserData*)fixtureA->GetUserData();
        if ( fud->getType() == FUD_EVENT_SENSOR ) {
            EventSensor* es = (EventSensor*)fud;
            eventSensorId = es->id;
            actorId = EA_BUDDY;
            eventSensorFixture = fixtureA;
        }
    }

    //car
    else if ( fixtureA == m_carLowerFixture && fixtureB->GetUserData() ) {
        FixtureUserData* fud = (FixtureUserData*)fixtureB->GetUserData();
        if ( fud->getType() == FUD_EVENT_SENSOR ) {
            EventSensor* es = (EventSensor*)fud;
            eventSensorId = es->id;
            actorId = EA_CAR;
            eventSensorFixture = fixtureB;
        }
    }
    else if ( fixtureB == m_carLowerFixture && fixtureA->GetUserData() ) {
        FixtureUserData* fud = (FixtureUserData*)fixtureA->GetUserData();
        if ( fud->getType() == FUD_EVENT_SENSOR ) {
            EventSensor* es = (EventSensor*)fud;
            eventSensorId = es->id;
            actorId = EA_CAR;
            eventSensorFixture = fixtureA;
        }
    }

    if ( actorId > -1 && eventSensorId > -1 ) {
        bool deleteEventSensor = runEvent(actorId, eventSensorId);
        if ( deleteEventSensor )
            m_fixturesToDeleteAfterStep.push_back( eventSensorFixture );
    }
}


