#include "game.h"
#include <onidev.h>
#include <onidev/graphics/render_state.h>
#include <stack>
#include <cstdlib>
#include <cmath>

od::Mat3 getCameraMatrix(const od::View& view, bool flip)
{
    if(flip) {
        return od::Mat3::ortho(
                    view.left(), view.right(),
                    view.bottom(), view.top());
    } else {
        return od::Mat3::ortho(
                    view.left(), view.right(),
                    view.top(), view.bottom());
    }
}

namespace jewels
{

Game::Game(GameAssets& assets, od::Sizeu size):
   _renderer(&assets.shader_pack, &assets.tex_pack),
   _size(size),
   _cells(size.wid*size.hei),
   _stable(false),
   _swapped(false)
{
    srand(0);
    
    for(unsigned i=0; i<size.wid; ++i)
    for(unsigned j=0; j<size.hei; ++j)
    {
        _cells[i + j*size.wid].jewel = 1 + (rand() % Jewel::count());
    }
}

bool Game::mouseCell(Posu& p) const
{
    int mx = _mouse.x / CellSize;
    int my = _mouse.y / CellSize;
    
    if(mx >= 0 && unsigned(mx) < _size.wid
    && my >= 0 && unsigned(my) < _size.hei)
    {
        p = Posu(mx, my);
        return true;
    }
    return false;
}

bool Game::mousePressCell(Posu& p) const
{
    if(od::mousePressed(od::mb_left))
    {
        return mouseCell(p) && !at(p).empty();
    }
    return false;
}

Cell& Game::at(Posu p)
{
    return _cells[p.x + p.y*_size.wid];
}

const Cell& Game::at(Posu p) const
{
    return _cells[p.x + p.y*_size.wid];
}

bool Game::eat(Posu p, int min)
{
    if(at(p).empty()) {
        return false;
    }
    
    Jewel jewel = at(p).jewel;
    
    // horizontal test
    int left =-1, right =-1;
    for(unsigned i=p.x; i<_size.wid; ++i)
    {
        if(!at(Posu(i, p.y)).dying() && at(Posu(i, p.y)).jewel == jewel) {
            right++;
        } else {
            break;
        }
    }
    for(int i=p.x; i>=0; --i)
    {
        if(!at(Posu(i, p.y)).dying() && at(Posu(i, p.y)).jewel == jewel) {
            left++;
        } else {
            break;
        }
    }
    
    // vertical test
    int top=-1, bottom=-1;
    for(unsigned i=p.y; i<_size.hei; ++i)
    {
        if(!at(Posu(p.x, i)).dying() && at(Posu(p.x, i)).jewel == jewel) {
            bottom++;
        } else {
            break;
        }
    }
    for(int i=p.y; i>=0; --i)
    {
        if(!at(Posu(p.x, i)).dying() && at(Posu(p.x, i)).jewel == jewel) {
            top++;
        } else {
            break;
        }
    }
    
    bool ret = false;
    if(left + right + 1 >= 3)
    {
        for(int i=p.x-left; i<=int(p.x)+right; ++i)
        {
            at(Posu(i, p.y)).scale = 0.999f; // prepare to diiie
        }
        ret = true;
    }
    if(top + bottom + 1 >= 3)
    {
        for(int i=p.y-top; i<=int(p.y)+bottom; ++i)
        {
            at(Posu(p.x, i)).scale = 0.999f;
        }
        ret = true;
    }
    return ret;
}

void Game::swap()
{
    std::swap(swap1, swap2);
    _swapped = true;
}

void Game::updateGamePhysics(float dt)
{
    float gravity = 0.3;
    float maxVy = 16;
    
    for(unsigned i=0; i<_size.wid; ++i)
    for(unsigned j=0; j<_size.hei; ++j)
    {
        Cell& c = _cells[i + j*_size.wid];
        if(!c.empty())
        {
            // if bottom is empty
            Posu p(i, j+1);
            if(j+1 != _size.hei
            && !at(p).dying()
            && (at(p).jewel == Jewel(0) || at(p).vy > 0))
            {
                Cell& down = _cells[i + (j+1)*_size.wid];
                if(c.dy >= 16)
                {
                    down.jewel = c.jewel;
                    down.dy = -32 + c.dy;
                    down.vy = c.vy;
                    c = Cell();
                }
                else
                {
                    c.dy += c.vy;
                    c.vy += gravity * dt;
                    if(c.vy > maxVy) {
                        c.vy = maxVy;
                    }
                }
                _stable = false;
            }
            else if(c.dy < 0)
            {
                c.dy += c.vy;
                c.vy += gravity * dt;
                
                if(c.vy > maxVy) {
                    c.vy = maxVy;
                }
                
                if(c.dy > 0)
                {
                    c.dy = 0;
                    c.vy = 0;
                    eat(Posu(i, j), 3);
                }
                _stable = false;
            }
        }
    }
}

void Game::spawnGems()
{
    for(unsigned i=0; i<_size.wid; ++i)
    {
        if(_cells[i].empty() && _cells[i+_size.wid].dy >= 0)
        {
            _cells[i] = Cell(1 + (rand()%Jewel::count()));
        }
    }
}

void Game::updateSoftDieEffect(float dt)
{
    for(unsigned i=0; i<_size.wid*_size.hei; ++i)
    {
        if(_cells[i].scale < 1 && _cells[i].scale > 0)
        {
            _cells[i].scale -= 0.1 * dt;
        }
        if(_cells[i].scale < 0)
        {
            _cells[i] = Cell();
            _cells[i].jewel = 0;
        }
    }
}

bool Game::swapSelectedGems(float dt)
{
    if(!_valid1 || !_valid2) {
        return false;
    }
    
    Cell& c1 = at(swap1);
    Cell& c2 = at(swap2);
    
    auto swapJewels = [&]() {
        Jewel jewel = c1.jewel;
        c1.jewel = c2.jewel;
        c2.jewel = jewel;
    };
    
    if(c1.dy == 0)
    {
        float s = od::sign(c1.dx);
        c1.dx += s * 2 * dt;
        c2.dx -= s * 2 * dt;
        
        if( std::fabs(c1.dx) >= 32 )
        {
            swapJewels();
            
            if( _swapped || (eat(swap1, 3) | eat(swap2, 3)) )
            {
                c1.dx = c2.dx = 0;
                swap1 = swap2 = Posu();
                _valid1 = _valid2 = false;
            }
            else
            {
                c2.dx =-s * 2 * dt;
                c1.dx = s * 2 * dt;
                swap();
            }
        }
    }
    else if(c1.dx == 0)
    {
        float s = od::sign(c1.dy);
        c1.dy += s * 2 * dt;
        c2.dy -= s * 2 * dt;
        
        if( std::fabs(c1.dy) >= 32 )
        {
            swapJewels();
            if( _swapped || (eat(swap1, 3) | eat(swap2, 3)) )
            {
                c1.dy = c2.dy = 0;
                swap1 = swap2 = Posu();
                _valid1 = _valid2 = false;
            }
            else
            {
                c2.dy =-s * 2 * dt;
                c1.dy = s * 2 * dt;
                swap();
            }
        }
    }
    
    return true;
}

void Game::selectGems(float dt)
{
    Posu p;
    
    if(!stable() || !mouseCell(p)) {
        return;
    }
    
    if(!od::mousePressed(od::mb_left)
    && !(od::mouseReleased(od::mb_left)
    && !_valid1)) {
        return;
    }
    
    if(!_valid1)
    {
        swap1 = p;
        _valid1 = true;
        _swapped = false;
    }
    else
    {
        if(aligned(od::Posi(p), od::Posi(swap1))
        && distance(od::Posi(p), od::Posi(swap1)) == 1)
        {
            od::Posi d = od::Posi(swap1) - od::Posi(p);
            swap2 = p;
            _valid2 = true;
            at(swap1).dx = -2 * dt * od::sign(d.x);
            at(swap1).dy = -2 * dt * od::sign(d.y);
        }
        else
        {
            swap1 = p;
            _valid1 = true;
        }
    }
}

bool Game::aligned(od::Posi p1, od::Posi p2)
{
    od::Posi d = p2 - p1;
    return d.x == 0 || d.y == 0;
}

float Game::distance(od::Posi p1, od::Posi p2)
{
    return sqrtf( (p2.x-p1.x)*(p2.x-p1.x) + (p2.y-p1.y)*(p2.y-p1.y) );
}

void Game::step(od::Posi mouse, float dt)
{
    _mouse = mouse;
    _stable = true;
    
    updateSoftDieEffect(dt);
    
    if(!swapSelectedGems(dt))
    {
        updateGamePhysics(dt);
        spawnGems();
        selectGems(dt);
    }
}

void Game::predraw(const od::View& view)
{
    _renderer.setDefaultCamera2d(getCameraMatrix(view, true));
    _renderer.clear();
    
    {
        od::RenderState rs(_renderer, 0, "simple", "sprites");
        
        for(unsigned i=0; i<_size.wid; ++i)
        for(unsigned j=0; j<_size.hei; ++j)
        {
            rs.draw("tiles", 0, od::Pos(i*CellSize, j*CellSize));
        }
        
        for(unsigned i=0; i<_size.wid; ++i)
        for(unsigned j=0; j<_size.hei; ++j)
        {
            Cell c = _cells[i + j*_size.wid];
            if(!c.jewel.empty()) {
                rs.drawScaled(
                        "jewels", c.jewel.color,
                        od::Pos(i*CellSize + c.dx + 16, j*CellSize + c.dy + 16),
                        od::Vec2(c.scale, c.scale));
            }
        }
        
        if(_valid1)
        {
            if(_swapped) {
                rs.draw("cursor", 0, od::Pos(swap2) * CellSize);
            } else {
                rs.draw("cursor", 0, od::Pos(swap1) * CellSize);
            }
        }
    }
    
    _renderer.complete();
}

void Game::draw() const
{
    _renderer.render(0);
}

}