/*Copyright (C) 2010-2020 Duncan Deveaux

This file is part of Hikou no mizu.

Hikou no mizu is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Hikou no mizu is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Hikou no mizu.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "Window.hpp"

Window::Window(const std::string &title) :
m_title(title), m_window(NULL), m_glContext(NULL), m_viewWidth(0), m_viewHeight(0), m_latency(0.f)
{
    
}

bool Window::create(int width, int height, bool fullscreen, int msBuffers, int msSamples)
{
    Uint32 flags = SDL_WINDOW_RESIZABLE | SDL_WINDOW_OPENGL;
    if (fullscreen) flags |= SDL_WINDOW_FULLSCREEN;
    
    // Enable multisampling
    SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, msBuffers);
    SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, msSamples);
    
    m_window = SDL_CreateWindow(m_title.c_str(),
                                SDL_WINDOWPOS_UNDEFINED,
                                SDL_WINDOWPOS_UNDEFINED,
                                width, height, flags);
    
    if (m_window == NULL)
    {
        std::cout << "Could not create window: " << SDL_GetError() << std::endl;
        if (msBuffers != 0 || msSamples != 0)
        {
            std::cout << "Attempt to disable multisampling..." << std::endl;
            return create(width, height, fullscreen, 0, 0);
        }
        
        return false;
    }
    
    // Create OpenGL Context
    m_glContext = SDL_GL_CreateContext(m_window);
    if (m_glContext == NULL)
    {
        std::cout << "Could not create OpenGL context: " << SDL_GetError() << std::endl;
        return false;
    }
    
    //GL parameters
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    
    return true;
}

void Window::destroy()
{
    SDL_GL_DeleteContext(m_glContext);
    SDL_DestroyWindow(m_window);
}

void Window::resizeWindow(int windowWidth, int windowHeight, bool fullscreen)
{
    SDL_SetWindowFullscreen(m_window, fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
    SDL_SetWindowSize(m_window, windowWidth, windowHeight);
    
    initView(windowWidth, windowHeight);
}

void Window::initView(float viewWidth, float viewHeight)
{
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    
    gluOrtho2D(0, viewWidth, viewHeight, 0);
    
    //Init viewport
    int windowWidth = 0, windowHeight = 0;
    SDL_GetWindowSize(m_window, &windowWidth, &windowHeight);
    
    float windowRatio = static_cast<float>(windowWidth) / windowHeight;
    float glRatio = viewWidth / viewHeight;
    
    if (windowRatio > glRatio)
    {
        m_viewPort.width = viewWidth * (windowHeight / viewHeight);
        m_viewPort.height = windowHeight;
        
        m_viewPort.left = (windowWidth - m_viewPort.width) / 2;
        m_viewPort.top = 0;
    }
    else
    {
        m_viewPort.width = windowWidth;
        m_viewPort.height = viewHeight * (windowWidth / viewWidth);
        
        m_viewPort.left = 0;
        m_viewPort.top = (windowHeight - m_viewPort.height) / 2;
    }
    
    glViewport(m_viewPort.left, m_viewPort.top, m_viewPort.width, m_viewPort.height);
    
    m_viewWidth = viewWidth;
    m_viewHeight = viewHeight;
}

void Window::resizeView()
{
    initView(m_viewWidth, m_viewHeight);
}

void Window::clear(const Color &color)
{
    //The range for colors in glClearColor() is [0 ; 1].
    GLfloat red = static_cast<GLfloat>(color.red) / 255;
    GLfloat green = static_cast<GLfloat>(color.green) / 255;
    GLfloat blue = static_cast<GLfloat>(color.blue) / 255;
    GLfloat alpha = static_cast<GLfloat>(color.alpha) / 255;
    
    //Clear
    glClearColor(red, green, blue, alpha);
    glClear(GL_COLOR_BUFFER_BIT);
    
    //...view
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

void Window::flush()
{
    float waitingTime = m_latency - m_time.getTime();
    if (waitingTime > 0)
        Timer::sleep(waitingTime);
    
    m_time.reset();
    
    glFlush();
    SDL_GL_SwapWindow(m_window);
}

bool Window::pollEvent(SDL_Event &event)
{
    return SDL_PollEvent(&event);
}

bool Window::waitEvent(SDL_Event &event)
{
    return SDL_WaitEvent(&event);
}

Vector Window::viewCoords(const Vector &windowCoords) const
{
    Vector coords;
    coords.x = (windowCoords.x - m_viewPort.left) * (m_viewWidth / m_viewPort.width);
    coords.y = (windowCoords.y - m_viewPort.top) * (m_viewHeight / m_viewPort.height);
    
    return coords;
}

Vector Window::windowCoords(const Vector &viewCoords) const
{
    Vector coords;
    coords.x = viewCoords.x * (m_viewPort.width / m_viewWidth) + m_viewPort.left;
    coords.y = viewCoords.y * (m_viewPort.height / m_viewHeight) + m_viewPort.top;
    
    return coords;
}

//Get and set methods
void Window::setIcon(const std::string &iconPath)
{
    //Load surface
    SDL_Surface *surface = IMG_Load( iconPath.c_str() );
    if (!surface)
    {
        std::cout << "The texture file: " << iconPath << " could not be opened." << std::endl;
        return;
    }
    
    SDL_SetWindowIcon(m_window, surface);
    SDL_FreeSurface(surface);
}

void Window::setTitle(const std::string &title)
{
    SDL_SetWindowTitle(m_window, title.c_str());
    m_title = title;
}

const std::string &Window::getTitle() const
{
    return m_title;
}

void Window::setFrameRate(float frameRate)
{
    if (frameRate > 0)
        m_latency = 1000 / frameRate;
    else
        m_latency = 0;
}

float Window::getFrameRate() const
{
    if (m_latency > 0)
        return 1000 / m_latency;
    
    return 0;
}

int Window::getWidth() const
{
    int width = 0;
    SDL_GetWindowSize(m_window, &width, NULL);
    
    return width;
}

int Window::getHeight() const
{
    int height = 0;
    SDL_GetWindowSize(m_window, NULL, &height);
    
    return height;
}

float Window::getViewWidth() const
{
    return m_viewWidth;
}

float Window::getViewHeight() const
{
    return m_viewHeight;
}

std::vector<Vector> Window::getAvailableModes()
{
    std::vector<Vector> modes;
    
    int displayIndex = SDL_GetWindowDisplayIndex(m_window);
    int modesNo = SDL_GetNumDisplayModes(displayIndex);
    
    for (int modeIndex = 0; modeIndex < modesNo; modeIndex++)
    {
        SDL_DisplayMode mode = { SDL_PIXELFORMAT_UNKNOWN, 0, 0, 0, 0 };
        if (SDL_GetDisplayMode(displayIndex, modeIndex, &mode) == 0)
            modes.push_back( Vector(mode.w, mode.h) );
    }
    
    return modes;
}
