#include "precompiled.h"

#include "graphics/GameView.h"
#include "graphics/Model.h"
#include "graphics/Sprite.h"
#include "graphics/Terrain.h"
#include "graphics/Unit.h"
#include "graphics/UnitManager.h"
#include "lib/res/graphics/ogl_tex.h"
#include "maths/MathUtil.h"
#include "maths/scripting/JSInterface_Vector3D.h"
#include "ps/Game.h"
#include "ps/Interact.h"
#include "ps/Profile.h"
#include "renderer/Renderer.h"
#include "renderer/WaterManager.h"
#include "scripting/ScriptableComplex.inl"

#include "Aura.h"
#include "Collision.h"
#include "Entity.h"
#include "EntityFormation.h"
#include "EntityManager.h"
#include "EntityTemplate.h"
#include "EntityTemplateCollection.h"
#include "EventHandlers.h"
#include "Formation.h"
#include "FormationManager.h"
#include "PathfindEngine.h"
#include "ProductionQueue.h"
#include "TechnologyCollection.h"
#include "TerritoryManager.h"

#include <algorithm>

extern int g_xres, g_yres;

void CEntity::Render()
{
    if( !m_visible ) return;

    if( !m_orderQueue.empty() )
    {
        std::deque<CEntityOrder>::iterator it;
        CBoundingObject* destinationCollisionObject;
        float x0, y0, x, y;

        x = m_orderQueue.front().m_target_location.x;
        y = m_orderQueue.front().m_target_location.y;

        for( it = m_orderQueue.begin(); it < m_orderQueue.end(); it++ )
        {
            if( it->m_type == CEntityOrder::ORDER_PATROL )
                break;
            x = it->m_target_location.x;
            y = it->m_target_location.y;
        }
        destinationCollisionObject = GetContainingObject( CVector2D( x, y ) );

        glShadeModel( GL_FLAT );
        glBegin( GL_LINE_STRIP );

        glVertex3f( m_position.X, m_position.Y + 0.25f, m_position.Z );

        x = m_position.X;
        y = m_position.Z;

        for( it = m_orderQueue.begin(); it < m_orderQueue.end(); it++ )
        {
            x0 = x;
            y0 = y;
            x = it->m_target_location.x;
            y = it->m_target_location.y;
            rayIntersectionResults r;
            CVector2D fwd( x -x0, y -y0 );
            float l = fwd.Length();
            fwd = fwd.Normalize();
            CVector2D rgt = fwd.beta();
            if( GetRayIntersection( CVector2D( x0, y0 ), fwd, rgt, l, m_bounds->m_radius, destinationCollisionObject, &r ) )
            {
                glEnd();
                glBegin( GL_LINES );
                glColor3f( 1.0f, 0.0f, 0.0f );
                glVertex3f( x0 + fwd.x * r.distance, GetAnchorLevel( x0 + fwd.x * r.distance, y0 + fwd.y * r.distance ) + 0.25f, y0 + fwd.y * r.distance );
                glVertex3f( r.position.x, GetAnchorLevel( r.position.x, r.position.y ) + 0.25f, r.position.y );
                glEnd();
                glBegin( GL_LINE_STRIP );
                glVertex3f( x0, GetAnchorLevel( x0, y0 ), y0 );
            }
            switch( it->m_type )
            {
                case CEntityOrder::ORDER_GOTO:
                glColor3f( 1.0f, 0.0f, 0.0f );
                break;
                case CEntityOrder::ORDER_GOTO_COLLISION:
                glColor3f( 1.0f, 0.5f, 0.5f );
                break;
                case CEntityOrder::ORDER_GOTO_NOPATHING:
                case CEntityOrder::ORDER_GOTO_SMOOTHED:
                glColor3f( 0.5f, 0.5f, 0.5f );
                break;
                case CEntityOrder::ORDER_PATROL:
                glColor3f( 0.0f, 1.0f, 0.0f );
                break;
                default:
                continue;
            }

            glVertex3f( x, GetAnchorLevel( x, y ) + 0.25f, y );
        }

        glEnd();
        glShadeModel( GL_SMOOTH );
    }

    glColor3f( 1.0f, 1.0f, 1.0f );
    if( GetCollisionObject( this ) )
        glColor3f( 0.5f, 0.5f, 1.0f );
    m_bounds->Render( GetAnchorLevel( m_position.X, m_position.Z ) + 0.25f ); //m_position.Y + 0.25f );
}

void CEntity::RenderSelectionOutline( float alpha )
{
    if( !m_bounds || !m_visible )
        return;

    if( GetCollisionObject( m_bounds, m_player, &m_base->m_socket ) )
    {
        glColor4f( 1.0f, 0.5f, 0.5f, alpha );   // We're colliding with another unit; colour outline pink
    }
    else
    {
        const SPlayerColour& col = m_player->GetColour();
        glColor3f( col.r, col.g, col.b );       // Colour outline with player colour
    }

    glBegin( GL_LINE_LOOP );

    CVector3D pos = m_graphics_position;

    switch( m_bounds->m_type )
    {
        case CBoundingObject::BOUND_CIRCLE:
        {
            float radius = ((CBoundingCircle*)m_bounds)->m_radius;
            for( int i = 0; i < SELECTION_CIRCLE_POINTS; i++ )
            {
                float ang = i * 2 * PI / (float)SELECTION_CIRCLE_POINTS;
                float x = pos.X + radius * sin( ang );
                float y = pos.Z + radius * cos( ang );
#ifdef SELECTION_TERRAIN_CONFORMANCE

                glVertex3f( x, GetAnchorLevel( x, y ) + 0.25f, y );
#else

                glVertex3f( x, pos.Y + 0.25f, y );
#endif

            }
            break;
        }
        case CBoundingObject::BOUND_OABB:
        {
            CVector2D p, q;
            CVector2D u, v;
            q.x = pos.X;
            q.y = pos.Z;
            float d = ((CBoundingBox*)m_bounds)->m_d;
            float w = ((CBoundingBox*)m_bounds)->m_w;

            u.x = sin( m_graphics_orientation.Y );
            u.y = cos( m_graphics_orientation.Y );
            v.x = u.y;
            v.y = -u.x;

#ifdef SELECTION_TERRAIN_CONFORMANCE

            for( int i = SELECTION_BOX_POINTS; i > -SELECTION_BOX_POINTS; i--)
            {
                p = q + u * d + v * ( w * (float)i / (float)SELECTION_BOX_POINTS );
                glVertex3f( p.x, GetAnchorLevel( p.x, p.y ) + 0.25f, p.y );
            }

            for( int i = SELECTION_BOX_POINTS; i > -SELECTION_BOX_POINTS; i--)
            {
                p = q + u * ( d * (float)i / (float)SELECTION_BOX_POINTS ) -v * w;
                glVertex3f( p.x, GetAnchorLevel( p.x, p.y ) + 0.25f, p.y );
            }

            for( int i = -SELECTION_BOX_POINTS; i < SELECTION_BOX_POINTS; i++ )
            {
                p = q -u * d + v * ( w * (float)i / (float)SELECTION_BOX_POINTS );
                glVertex3f( p.x, GetAnchorLevel( p.x, p.y ) + 0.25f, p.y );
            }

            for( int i = -SELECTION_BOX_POINTS; i < SELECTION_BOX_POINTS; i++ )
            {
                p = q + u * ( d * (float)i / (float)SELECTION_BOX_POINTS ) + v * w;
                glVertex3f( p.x, GetAnchorLevel( p.x, p.y ) + 0.25f, p.y );
            }
#else
            p = q + u * h + v * w;
            glVertex3f( p.x, GetAnchorLevel( p.x, p.y ) + 0.25f, p.y );

            p = q + u * h -v * w;
            glVertex3f( p.x, GetAnchorLevel( p.x, p.y ) + 0.25f, p.y );

            p = q -u * h + v * w;
            glVertex3f( p.x, GetAnchorLevel( p.x, p.y ) + 0.25f, p.y );

            p = q + u * h + v * w;
            glVertex3f( p.x, GetAnchorLevel( p.x, p.y ) + 0.25f, p.y );
#endif


            break;
        }
    }

    glEnd();
}

void CEntity::RenderAuras()
{
    if( !(m_bounds && m_visible && !m_auras.empty()) )
        return;
    
    const SPlayerColour& playerCol = m_player->GetColour();
    glPushMatrix();
    glTranslatef(m_graphics_position.X, m_graphics_position.Y, 
                                        m_graphics_position.Z);

    size_t i=0;

    for ( AuraTable::iterator it=m_auras.begin(); it!=m_auras.end(); ++it, ++i )
    {
        CVector4D color = it->second->m_color;
        glColor4f(color.m_X, color.m_Y, color.m_Z, color.m_W);

#ifdef SELECTION_TERRAIN_CONFORMANCE
        //This starts to break when the radius is bigger
        if ( it->second->m_radius < 15.0f )
        {
            glBegin(GL_TRIANGLE_FAN);
            glVertex3f(0.0f, GetAnchorLevel(m_graphics_position.X, 
                    m_graphics_position.Z)-m_graphics_position.Y+.5f, 0.0f);
            for ( int j=0; j<AURA_CIRCLE_POINTS; ++j )
            {
                CVector2D ypos( m_unsnappedPoints[i][j].x+m_graphics_position.X,
                            m_unsnappedPoints[i][j].y+m_graphics_position.Z );
                CVector3D pos( m_unsnappedPoints[i][j].x, GetAnchorLevel(ypos.x, ypos.y)-m_graphics_position.Y+.5f, m_unsnappedPoints[i][j].y );
                glVertex3f(pos.X, pos.Y, pos.Z);
            }
            //Loop around
            CVector3D pos( m_unsnappedPoints[i][0].x, 
                    GetAnchorLevel(m_unsnappedPoints[i][0].x+m_graphics_position.X, 
                    m_unsnappedPoints[i][0].y+m_graphics_position.Z)-m_graphics_position.Y+.5f, m_unsnappedPoints[i][0].y );
            glVertex3f(pos.X, pos.Y, pos.Z);
            glEnd();    // GL_TRIANGLE_FAN
        }

        //Draw edges
        glEnable(GL_LINE_SMOOTH);
        glLineWidth(1.0f);
        glBegin(GL_LINE_LOOP);
        glColor3f( playerCol.r, playerCol.g, playerCol.b );
        for ( int j=0; j<AURA_CIRCLE_POINTS; ++j )
        {
            CVector2D ypos( m_unsnappedPoints[i][j].x+m_graphics_position.X,
                        m_unsnappedPoints[i][j].y+m_graphics_position.Z );
            CVector3D pos( m_unsnappedPoints[i][j].x, GetAnchorLevel(ypos.x, ypos.y)-m_graphics_position.Y+.5f, m_unsnappedPoints[i][j].y );
            glVertex3f(pos.X, pos.Y, pos.Z);
        }
        glEnd();
        glDisable(GL_LINE_SMOOTH);
#else
        if ( it->second->m_radius < 15.0f )
        {
            for ( int j=0; j<SELECTION_CIRLCE_POINTS; ++j )
                glVertex3f(m_unsnappedPoints[i][j].x, .25f, m_unsnappedPoints[i][j].y);
            glVertex3f(m_unsnappedPoints[i][0].x, .25f, m_unsnappedPoints[i][0].y);
        }
        glEnd();
        
        //Draw edges
        glBegin(GL_LINE_LOOP);
        glColor3f( col.r, col.g, col.b );
        for ( int j=0; j<SELECTION_CIRLCE_POINTS; ++j )
            glVertex3f(unsnappedPoints[i][j].x, .25f, m_unsnappedPoints[i][j].y);
        glEnd();
#endif 
    }
    glPopMatrix();
}

CVector2D CEntity::GetScreenCoords( float height )
{
    CCamera &camera = *g_Game->GetView()->GetCamera();

    float sx, sy;
    CVector3D above;
    above.X = m_position.X;
    above.Z = m_position.Z;
    above.Y = GetAnchorLevel(m_position.X, m_position.Z) + height;
    camera.GetScreenCoordinates(above, sx, sy);
    return CVector2D( sx, sy );
}

void CEntity::DrawRect( CVector3D& centre, CVector3D& up, CVector3D& right, float x1, float y1, float x2, float y2 )
{
    glBegin(GL_QUADS);
    const int X[] = {1,1,0,0};  // which X and Y to choose at each vertex
    const int Y[] = {0,1,1,0};
    for( int i=0; i<4; i++ ) 
    {
        CVector3D vec = centre + right * (X[i] ? x1 : x2) + up * (Y[i] ? y1 : y2);
        glTexCoord2f( X[i], Y[i] );
        glVertex3fv( &vec.X );
    }
    glEnd();
}

void CEntity::DrawBar( CVector3D& centre, CVector3D& up, CVector3D& right, 
        float x1, float y1, float x2, float y2,
        SColour col1, SColour col2, float currVal, float maxVal )
{
    // Figure out fraction that should be col1
    float fraction;
    if(maxVal == 0) fraction = 1.0f;
    else fraction = clamp( currVal / maxVal, 0.0f, 1.0f );

    /*// Draw the border at full size
    ogl_tex_bind( g_Selection.m_unitUITextures[m_base->m_barBorder] );
    DrawRect( centre, up, right, x1, y1, x2, y2 );
    ogl_tex_bind( 0 );

    // Make the bar contents slightly smaller than the border
    x1 += m_base->m_barBorderSize;
    y1 += m_base->m_barBorderSize;
    x2 -= m_base->m_barBorderSize;
    y2 -= m_base->m_barBorderSize;*/
    
    // Draw the bar contents
    float xMid = x2 * fraction + x1 * (1.0f -fraction);
    glColor3fv( &col1.r );
    DrawRect( centre, up, right, x1, y1, xMid, y2 );
    glColor3fv( &col2.r );
    DrawRect( centre, up, right, xMid, y1, x2, y2 );
}

void CEntity::RenderBars()
{
    if( !m_base->m_barsEnabled || !m_bounds || !m_visible)
        return;

    SnapToGround();
    CVector3D centre = m_graphics_position;
    centre.Y += m_base->m_barOffset;
    CVector3D up = g_Game->GetView()->GetCamera()->m_Orientation.GetUp();
    CVector3D right = -g_Game->GetView()->GetCamera()->m_Orientation.GetLeft();

    float w = m_base->m_barWidth;
    float h = m_base->m_barHeight;
    float borderSize = m_base->m_barBorderSize;

    // Draw the health and stamina bars; if the unit has no stamina, the health bar is
    // drawn centered, otherwise it's offset slightly up and the stamina bar is offset
    // slightly down so that they overlap over an area of size borderSize.

    bool hasStamina = (m_staminaMax > 0);

    float backgroundW = w+2*borderSize;
    float backgroundH = hasStamina ? 2*h+2*borderSize : h+2*borderSize;
    ogl_tex_bind( g_Selection.m_unitUITextures[m_base->m_barBorder] );
    DrawRect( centre, up, right, -backgroundW/2, -backgroundH/2, backgroundW/2, backgroundH/2 );
    ogl_tex_bind( 0 );

    float off = hasStamina ? h/2 : 0;
    DrawBar( centre, up, right, -w/2, off-h/2, w/2, off+h/2, 
            SColour(0,1,0), SColour(1,0,0), m_healthCurr, m_healthMax );

    if( hasStamina ) 
    {
        DrawBar( centre, up, right, -w/2, -h, w/2, 0, 
                SColour(0,0,1), SColour(0.4f,0.4f,0.1f), m_staminaCurr, m_staminaMax );
    }

    // Draw the rank icon

    std::map<CStr, Handle>::iterator it = g_Selection.m_unitUITextures.find( m_rankName );
    if( it != g_Selection.m_unitUITextures.end() )
    {
        float size = 2*h + borderSize;
        ogl_tex_bind( it->second );
        DrawRect( centre, up, right, w/2+borderSize, -size/2, w/2+borderSize+size, size/2 );
        ogl_tex_bind( 0 );
    }
}

void CEntity::RenderBarBorders()
{ 
    if( !m_visible )
        return;

    if ( m_base->m_staminaBarHeight >= 0 && 
        g_Selection.m_unitUITextures.find(m_base->m_healthBorderName) != g_Selection.m_unitUITextures.end() )
    {
        ogl_tex_bind( g_Selection.m_unitUITextures[m_base->m_healthBorderName] );
        CVector2D pos = GetScreenCoords( m_base->m_healthBarHeight );

        float left = pos.x -m_base->m_healthBorderWidth/2;
        float right = pos.x + m_base->m_healthBorderWidth/2;
        pos.y = g_yres -pos.y;
        float bottom = pos.y + m_base->m_healthBorderHeight/2;
        float top = pos.y -m_base->m_healthBorderHeight/2;

        glBegin(GL_QUADS);

        glTexCoord2f(0.0f, 0.0f); glVertex3f( left, bottom, 0 );
        glTexCoord2f(0.0f, 1.0f); glVertex3f( left, top, 0 );
        glTexCoord2f(1.0f, 1.0f); glVertex3f( right, top, 0 );
        glTexCoord2f(1.0f, 0.0f); glVertex3f( right, bottom, 0 );

        glEnd();
    }
    if ( m_base->m_staminaBarHeight >= 0 && 
        g_Selection.m_unitUITextures.find(m_base->m_staminaBorderName) != g_Selection.m_unitUITextures.end() )
    {
        ogl_tex_bind( g_Selection.m_unitUITextures[m_base->m_staminaBorderName] );

        CVector2D pos = GetScreenCoords( m_base->m_staminaBarHeight );
        float left = pos.x -m_base->m_staminaBorderWidth/2;
        float right = pos.x + m_base->m_staminaBorderWidth/2;
        pos.y = g_yres -pos.y;
        float bottom = pos.y + m_base->m_staminaBorderHeight/2;
        float top = pos.y -m_base->m_staminaBorderHeight/2;

        glBegin(GL_QUADS);

        glTexCoord2f(0.0f, 0.0f); glVertex3f( left, bottom, 0 );
        glTexCoord2f(0.0f, 1.0f); glVertex3f( left, top, 0 );
        glTexCoord2f(1.0f, 1.0f); glVertex3f( right, top, 0 );
        glTexCoord2f(1.0f, 0.0f); glVertex3f( right, bottom, 0 );

        glEnd();
    }
}

void CEntity::RenderHealthBar()
{
    if( !m_bounds || !m_visible )
        return;
    if( m_base->m_healthBarHeight < 0 )
        return;  // negative bar height means don't display health bar

    float fraction;
    if(m_healthMax == 0) fraction = 1.0f;
    else fraction = clamp(m_healthCurr / m_healthMax, 0.0f, 1.0f);

    CVector2D pos = GetScreenCoords( m_base->m_healthBarHeight );
    float x1 = pos.x -m_base->m_healthBarSize/2;
    float x2 = pos.x + m_base->m_healthBarSize/2;
    float y = g_yres -pos.y;

    glLineWidth( m_base->m_healthBarWidth );
    glBegin(GL_LINES);

    // green part of bar
    glColor3f( 0, 1, 0 );
    glVertex3f( x1, y, 0 );
    glColor3f( 0, 1, 0 );
    glVertex3f( x1 + m_base->m_healthBarSize*fraction, y, 0 );

    // red part of bar
    glColor3f( 1, 0, 0 );
    glVertex3f( x1 + m_base->m_healthBarSize*fraction, y, 0 );
    glColor3f( 1, 0, 0 );
    glVertex3f( x2, y, 0 );

    glEnd();

    glLineWidth(1.0f);
}

void CEntity::RenderStaminaBar()
{
    if( !m_bounds || !m_visible )
        return;
    if( m_base->m_staminaBarHeight < 0 )
        return;  // negative bar height means don't display stamina bar

    float fraction;
    if(m_staminaMax == 0) fraction = 1.0f;
    else fraction = clamp(m_staminaCurr / m_staminaMax, 0.0f, 1.0f);

    CVector2D pos = GetScreenCoords( m_base->m_staminaBarHeight );
    float x1 = pos.x -m_base->m_staminaBarSize/2;
    float x2 = pos.x + m_base->m_staminaBarSize/2;
    float y = g_yres -pos.y;

    glLineWidth( m_base->m_staminaBarWidth );
    glBegin(GL_LINES);

    // blue part of bar
    glColor3f( 0.1f, 0.1f, 1 );
    glVertex3f( x1, y, 0 );
    glColor3f( 0.1f, 0.1f, 1 );
    glVertex3f( x1 + m_base->m_staminaBarSize*fraction, y, 0 );

    // purple part of bar
    glColor3f( 0.3f, 0, 0.3f );
    glVertex3f( x1 + m_base->m_staminaBarSize*fraction, y, 0 );
    glColor3f( 0.3f, 0, 0.3f );
    glVertex3f( x2, y, 0 );

    glEnd();
    glLineWidth(1.0f);
}

void CEntity::RenderRank()
{
    if( !m_bounds || !m_visible )
        return;
    if( m_base->m_rankHeight < 0 )
        return;  // negative height means don't display rank
    //Check for valid texture
    if( g_Selection.m_unitUITextures.find( m_rankName ) == g_Selection.m_unitUITextures.end() )
        return;

    CCamera *camera = g_Game->GetView()->GetCamera();

    float sx, sy;
    CVector3D above;
    above.X = m_position.X;
    above.Z = m_position.Z;
    above.Y = GetAnchorLevel(m_position.X, m_position.Z) + m_base->m_rankHeight;
    camera->GetScreenCoordinates(above, sx, sy);
    int size = m_base->m_rankWidth/2;

    float x1 = sx + m_base->m_healthBarSize/2;
    float x2 = sx + m_base->m_healthBarSize/2 + 2*size;
    float y1 = g_yres -(sy -size);  //top
    float y2 = g_yres -(sy + size); //bottom

    ogl_tex_bind(g_Selection.m_unitUITextures[m_rankName]);
    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
    glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_REPLACE);
    glTexEnvf(GL_TEXTURE_FILTER_CONTROL, GL_TEXTURE_LOD_BIAS, g_Renderer.m_Options.m_LodBias);

    glBegin(GL_QUADS);

    glTexCoord2f(1.0f, 0.0f); glVertex3f( x2, y2, 0 );
    glTexCoord2f(1.0f, 1.0f); glVertex3f( x2, y1, 0 );
    glTexCoord2f(0.0f, 1.0f); glVertex3f( x1, y1, 0 );
    glTexCoord2f(0.0f, 0.0f); glVertex3f( x1, y2, 0 );

    glEnd();
}

void CEntity::RenderRallyPoint()
{
    if( !m_visible )
        return;

    if ( !entf_get(ENTF_HAS_RALLY_POINT) || g_Selection.m_unitUITextures.find(m_base->m_rallyName) == 
                            g_Selection.m_unitUITextures.end() )
    {
        return;
    }
    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
    glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_REPLACE);
    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_TEXTURE);
    glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR);

    glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE);
    glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_TEXTURE);    
    glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA);
    glTexEnvf(GL_TEXTURE_FILTER_CONTROL, GL_TEXTURE_LOD_BIAS, g_Renderer.m_Options.m_LodBias);

    CSprite sprite;
    CTexture tex;
    tex.SetHandle( g_Selection.m_unitUITextures[m_base->m_rallyName] );
    sprite.SetTexture(&tex);
    CVector3D rally = m_rallyPoint;
    rally.Y += m_base->m_rallyHeight/2.f + .1f;
    sprite.SetTranslation(rally);
    sprite.Render();
}