#include <algorithm>
#include <vector>
#include <cassert>
#include <cstdio>

#include "system.h"
namespace ogl2d
{

struct SystemList
{
    ::ogl2d::System* m_pSystem;
    bool operator == (HWND hWnd) const
    {
        return m_pSystem->GetHWnd() == hWnd;
    }
    bool operator == (::ogl2d::System* pSystem) const
    {
        return m_pSystem == pSystem;
    }
};

static ::std::vector< SystemList > s_Systems;

void System::ShutDown()
{
    for (::std::vector< SystemList >::iterator sys = s_Systems.begin(); sys != s_Systems.end(); ++sys)
    {
        delete sys->m_pSystem;
    }
    s_Systems.clear();
}

void CALLBACK callback_reshape(GLsizei w, GLsizei h)
{

    glMatrixMode(GL_PROJECTION);    /* prepare for and then */ 
    glLoadIdentity();               /* define the projection */
    glOrtho(0, w, h, 0, -100.0f, 100.0);/* transformation */
    glMatrixMode(GL_MODELVIEW);  /* back to modelview matrix */
    glLoadIdentity();
    glViewport(0, 0, w, h);      /* define the viewport */

}

System* System::GetInstance(HWND hWnd)
{
    System* pSys = NULL;
    ::std::vector< SystemList >::iterator sys = ::std::find(s_Systems.begin(), s_Systems.end(), hWnd);
    if (sys == s_Systems.end())
    {
        if (s_Systems.empty())
        {
            extern void initbspline();
            initbspline();
        }
        pSys = new System(hWnd);
        SystemList node;
        node.m_pSystem = pSys;
        s_Systems.push_back(node);
    }
    else
    {
        pSys = sys->m_pSystem;
    }

    return pSys;
}

void System::ReleaseInstance(System*& pSystem)
{
    if (pSystem)
    {
        delete pSystem; 
        ::std::vector< SystemList >::iterator sys = ::std::find(s_Systems.begin(), s_Systems.end(), pSystem);
        assert(sys != s_Systems.end());
        s_Systems.erase(sys);
        pSystem = NULL; 
    }
}

System::~System()
{
    Release();
}

System::System(HWND hWnd):
    lastTextureId(-1), m_hWnd(hWnd), m_hDC(0), m_hRC(0), m_bWindowValid(false), m_bFullScreen(false)
{
}

void System::Release()                                // Properly Kill The Window
{
    if (m_bFullScreen)                                        // Are We In Fullscreen Mode?
    {
        ::ChangeDisplaySettings(NULL,0);                    // If So Switch Back To The Desktop
        ::ShowCursor(TRUE);                                // Show Mouse Pointer
    }

    if (m_hRC)                                            // Do We Have A Rendering Context?
    {
        if (!::wglMakeCurrent(NULL,NULL))                    // Are We Able To Release The DC And RC Contexts?
        {
            ::MessageBox(NULL,"Release Of DC And RC Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
        }

        if (!::wglDeleteContext(m_hRC))                        // Are We Able To Delete The RC?
        {
            ::MessageBox(NULL,"Release Rendering Context Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
        }
        m_hRC=NULL;                                        // Set RC To NULL
    }

    if (m_hDC)
    {
        ::ReleaseDC(m_hWnd, m_hDC);                       // Are We Able To Release The DC
        m_hDC = NULL;                                        // Set DC To NULL
    }
/*
    if (hWnd && !DestroyWindow(hWnd))                    // Are We Able To Destroy The Window?
    {
        MessageBox(NULL,"Could Not Release hWnd.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
        hWnd=NULL;                                        // Set hWnd To NULL
    }

    if (!UnregisterClass("OpenGL",hInstance))            // Are We Able To Unregister Class
    {
        MessageBox(NULL,"Could Not Unregister Class.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
        hInstance=NULL;                                    // Set hInstance To NULL
    }*/

}


bool System::Create(int width, int height, bool bFullScreen)
{
    int bpp = 32;

    if (bFullScreen)                                             // Attempt Fullscreen Mode?
    {
        DEVMODE dmScreenSettings;                                // Device Mode
        memset(&dmScreenSettings, 0, sizeof(dmScreenSettings));  // Makes Sure Memory's Cleared
        dmScreenSettings.dmSize = sizeof(dmScreenSettings);      // Size Of The Devmode Structure
        dmScreenSettings.dmPelsWidth = width;                    // Selected Screen Width
        dmScreenSettings.dmPelsHeight = height;                  // Selected Screen Height
        dmScreenSettings.dmBitsPerPel = bpp;                     // Selected Bits Per Pixel
        dmScreenSettings.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;

        // Try To Set Selected Mode And Get Results.  NOTE: CDS_FULLSCREEN Gets Rid Of Start Bar.
        if (ChangeDisplaySettings(&dmScreenSettings, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL)
        {
            // If The Mode Fails, Offer Two Options.  Quit Or Use Windowed Mode.
            if (MessageBox(NULL,
                "The Requested Fullscreen Mode Is Not Supported By\n"
                "Your Video Card. Use Windowed Mode Instead?",
                "NeHe GL",
                MB_YESNO|MB_ICONEXCLAMATION) == IDYES)
            {
                bFullScreen = false;        // Windowed Mode Selected.  Fullscreen = FALSE
            }
            else
            {
                // Pop Up A Message Box Letting User Know The Program Is Closing.
                MessageBox(NULL,
                    "Program Will Now Close.",
                    "ERROR",
                    MB_OK|MB_ICONSTOP);
                return false;                                    // Return FALSE
            }
        }
    }
    m_bFullScreen = bFullScreen;

    PIXELFORMATDESCRIPTOR pfd =
    {
        sizeof(PIXELFORMATDESCRIPTOR),
            1,                            
            PFD_DRAW_TO_WINDOW |        
            PFD_SUPPORT_OPENGL |   PFD_SWAP_LAYER_BUFFERS|     
            PFD_DOUBLEBUFFER,            
            PFD_TYPE_RGBA,                
            bpp,            
            0, 0, 0, 0, 0, 0,
            0,                
            0,                
            0,                
            0, 0, 0, 0,        
            16,                        //16 bit Z-Buffer
            8,          // 8 bit Stencil buffer
            0,
            PFD_MAIN_PLANE,    
            0,                
            0, 0, 0            
    };
    GLuint PixelFormat;
    m_hDC = ::GetDC(m_hWnd);
    if (m_hDC == 0)
        return false;
    
    if(!(PixelFormat = ::ChoosePixelFormat(m_hDC,&pfd)))
        return false;            
    /*
    DescribePixelFormat(m_hDC, PixelFormat, sizeof(PIXELFORMATDESCRIPTOR), &pfd);
    
    int generic_format = pfd.dwFlags & PFD_GENERIC_FORMAT;
    int generic_accelerated = pfd.dwFlags & PFD_GENERIC_ACCELERATED;
    if (generic_format && ! generic_accelerated)
    {
        MessageBox(m_hWnd, "显卡不支持OpenGL硬件加速,请升级显卡驱动", "Hardware acceleration detectation", MB_OK);
    }
    else if (generic_format && generic_accelerated)
    {
        MessageBox(m_hWnd, "OpenGL MCD硬件加速", "硬件加速检测", MB_OK);
    }
    else if (! generic_format && ! generic_accelerated)
    {
        MessageBox(m_hWnd, "OpenGL ICD硬件加速", "硬件加速检测", MB_OK);
    }
    //*/
    if(!::SetPixelFormat(m_hDC,PixelFormat, &pfd) ||
       !(m_hRC = ::wglCreateContext(m_hDC)) ||
       !::wglMakeCurrent(m_hDC,m_hRC) )
        return false;

    callback_reshape(width, height);
    glEnable(GL_TEXTURE_2D);
    glDisable(GL_DITHER ); 

    SetFrameRate();

    return true;
}

void System::Active()
{
    assert(m_hDC && m_hRC);
    ::wglMakeCurrent(m_hDC,m_hRC);
}

void System::Resize(int width, int height)
{
    callback_reshape(width, height);
}

void System::BeginScene()
{
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
    //glClear(GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();
}

void System::Flush(bool withClear)
{
    /*
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_TEXTURE_2D);
    glPushMatrix();
    glTranslatef(0,0,-5);
    normalPass.Render(lastTextureId);
    glPopMatrix();

    glDisable(GL_DEPTH_TEST);
    glTranslatef(0,0,-0.5);
    
    alphaPass.Render(lastTextureId);
    */
    glFlush();
    ::SwapBuffers(m_hDC);
    if (withClear)
    {
        BeginScene();
    }
}

void System::Pop(Clip *clip, int needAlpha)
{
    (needAlpha?alphaPass:normalPass).PopClip(clip);
}

void System::Push(Clip *clip, int needAlpha)
{
    (needAlpha?alphaPass:normalPass).PushClip(clip);
}

bool System::CheckFrameRateTime()
{
    DWORD now = timeGetTime() & 0x1fffffff; // 忽略高3位
    DWORD next = (m_LastFrameBeginTime + m_FrameRateDelay) >> 3;
    if (now >= next)
    {
        m_LastFrameBeginTime += m_FrameRateDelay;
        return true;
    }
    return false;
}

void System::SetFrameRate(int rate)
{
    m_LastFrameBeginTime = timeGetTime() << 3;
    m_FrameRateDelay = 8000 / rate;
}


}; // namespace ogl2d