#include "Resizer.h"

#include "Workspace.h"

#include <GG/DrawUtil.h>
#include <GG/GUI.h>

namespace {
    GG::Pt UpperLeftOf(const GG::Wnd* wnd, const GG::Wnd* parent)
    {
        return wnd->UpperLeft() +
            (GG::MatchesOrContains(parent, wnd) ? GG::Pt() : parent->ClientUpperLeft());
    }
}

////////////////////////////////////////////////
// Resizer
////////////////////////////////////////////////
// static(s)
std::set<Resizer*> Resizer::s_all_resizers;
bool Resizer::s_dragging_other_resizers = false;

Resizer::Resizer(WndPtr wnd, const Dialog* dialog) :
    Control((UpperLeftOf(wnd, dialog) - dialog->ClientUpperLeft()).x - RESIZE_SQUARE_WIDTH,
            (UpperLeftOf(wnd, dialog) - dialog->ClientUpperLeft()).y - RESIZE_SQUARE_WIDTH,
            wnd->Width() + 2 * RESIZE_SQUARE_WIDTH, wnd->Height() + 2 * RESIZE_SQUARE_WIDTH,
            GG::CLICKABLE),
    m_wnd(wnd),
    m_handle_being_dragged(HANDLE_NONE),
    m_drag_offset()
{
    assert(m_wnd);
    SetColor(GG::CLR_BLUE);
    s_all_resizers.insert(this);
}

Resizer::~Resizer()
{
    s_all_resizers.erase(this);
}

Resizer::WndPtr Resizer::GetWnd()
{
    return m_wnd;
}

void Resizer::Render()
{
    GG::Pt wnd_ul = WndUpperLeft();
    GG::Pt wnd_lr = WndLowerRight();
    if (UpperLeft() + GG::Pt(RESIZE_SQUARE_WIDTH, RESIZE_SQUARE_WIDTH) != wnd_ul ||
        LowerRight() - GG::Pt(RESIZE_SQUARE_WIDTH, RESIZE_SQUARE_WIDTH) != wnd_lr)
        SizeMove(wnd_ul - Parent()->ClientUpperLeft() - GG::Pt(RESIZE_SQUARE_WIDTH, RESIZE_SQUARE_WIDTH),
                 wnd_lr - Parent()->ClientUpperLeft() + GG::Pt(RESIZE_SQUARE_WIDTH, RESIZE_SQUARE_WIDTH));
    GG::Pt ul = UpperLeft(), lr = LowerRight();
    glDisable(GL_TEXTURE_2D);
    if (OnlyActiveResizer()) {
        for (Handle i = HANDLE_UL; i <= HANDLE_LR; i = Handle(i + 1)) {
            GG::Rect rect = HandleRect(i) + Parent()->ClientUpperLeft();
            GG::FlatRectangle(rect.ul.x, rect.ul.y,
                              rect.lr.x, rect.lr.y,
                              Color(), GG::CLR_ZERO, 0);
        }
    } else {
        GG::FlatRectangle(ul.x + RESIZE_SQUARE_WIDTH - 1, ul.y + RESIZE_SQUARE_WIDTH - 1,
                          lr.x - RESIZE_SQUARE_WIDTH + 1, lr.y - RESIZE_SQUARE_WIDTH + 1,
                          GG::CLR_ZERO, Color(), 1);
    }
    glEnable(GL_TEXTURE_2D);
}

void Resizer::LButtonDown(const GG::Pt& pt, Uint32 keys)
{
    m_original_ul = RelativeUpperLeft();
    m_original_size = Size();
    m_handle_being_dragged = HANDLE_NONE;
    GG::Rect rect = HandleRect(HANDLE_CLIENT_WND);
    GG::Pt rel_pt = pt - Parent()->ClientUpperLeft();
    if (rect.Contains(rel_pt)) {
        m_handle_being_dragged = HANDLE_CLIENT_WND;
        m_drag_offset = rel_pt - rect.ul;
        return;
    } else if (!OnlyActiveResizer()) {
        return;
    }
    for (Handle i = HANDLE_UL; i <= HANDLE_LR; i = Handle(i + 1)) {
        rect = HandleRect(i);
        if (rect.Contains(rel_pt)) {
            m_handle_being_dragged = i;
            m_drag_offset = rel_pt - rect.ul;
            break;
        }
    }
}

void Resizer::LDrag(const GG::Pt& pt, const GG::Pt& move, Uint32 keys)
{
    GG::Pt ul = RelativeUpperLeft(), lr = RelativeLowerRight();
    GG::Pt rel_pt = pt - Parent()->ClientUpperLeft();
    GG::Pt pt_offset = m_drag_offset;
    if (m_handle_being_dragged == HANDLE_CLIENT_WND) {
        pt_offset += HandleRect(m_handle_being_dragged).ul - ul;
        GG::Pt new_ul = rel_pt - pt_offset;
        GG::Pt new_lr = new_ul + Size();
        if (new_ul.x < -RESIZE_SQUARE_WIDTH)
            new_ul.x = -RESIZE_SQUARE_WIDTH;
        if (new_ul.y < -RESIZE_SQUARE_WIDTH)
            new_ul.y = -RESIZE_SQUARE_WIDTH;
        if (Parent()->ClientSize().x < new_lr.x - RESIZE_SQUARE_WIDTH)
            new_ul.x -= new_lr.x - RESIZE_SQUARE_WIDTH - Parent()->ClientSize().x;
        if (Parent()->ClientSize().y < new_lr.y - RESIZE_SQUARE_WIDTH)
            new_ul.y -= new_lr.y - RESIZE_SQUARE_WIDTH - Parent()->ClientSize().y;
        MoveTo(new_ul);
    } else {
        GG::Pt new_ul = ul, new_lr = lr;
        switch (m_handle_being_dragged) {
        case HANDLE_NONE:
        case HANDLE_CLIENT_WND:
            break;
        case HANDLE_UL:
            pt_offset += HandleRect(m_handle_being_dragged).ul - ul;
            new_ul = rel_pt - pt_offset;
            new_lr = lr;
            break;
        case HANDLE_TOP:
            pt_offset += HandleRect(m_handle_being_dragged).ul - ul;
            new_ul = GG::Pt(ul.x, rel_pt.y - pt_offset.y);
            new_lr = GG::Pt(lr.x, lr.y);
            break;
        case HANDLE_UR:
            pt_offset += HandleRect(m_handle_being_dragged).ul - ul;
            new_ul = GG::Pt(ul.x, rel_pt.y - pt_offset.y);
            new_lr = GG::Pt(rel_pt.x - pt_offset.x + Width(), lr.y);
            break;
        case HANDLE_LEFT:
            pt_offset += HandleRect(m_handle_being_dragged).ul - ul;
            new_ul = GG::Pt(rel_pt.x - pt_offset.x, ul.y);
            new_lr = GG::Pt(lr.x, lr.y);
            break;
        case HANDLE_RIGHT:
            pt_offset += HandleRect(m_handle_being_dragged).ul - ul;
            new_ul = GG::Pt(ul.x, ul.y);
            new_lr = GG::Pt(rel_pt.x - pt_offset.x + Width(), lr.y);
            break;
        case HANDLE_LL:
            pt_offset += HandleRect(m_handle_being_dragged).ul - ul;
            new_ul = GG::Pt(rel_pt.x - pt_offset.x, ul.y);
            new_lr = GG::Pt(lr.x, rel_pt.y - pt_offset.y + Height());
            break;
        case HANDLE_BOTTOM:
            pt_offset += HandleRect(m_handle_being_dragged).ul - ul;
            new_ul = GG::Pt(ul.x, ul.y);
            new_lr = GG::Pt(lr.x, rel_pt.y - pt_offset.y + Height());
            break;
        case HANDLE_LR:
            pt_offset += HandleRect(m_handle_being_dragged).ul - ul;
            new_ul = GG::Pt(ul);
            new_lr = GG::Pt(rel_pt - pt_offset + Size());
            break;
        }
        if (new_ul.x < -RESIZE_SQUARE_WIDTH)
            new_ul.x = -RESIZE_SQUARE_WIDTH;
        if (new_ul.y < -RESIZE_SQUARE_WIDTH)
            new_ul.y = -RESIZE_SQUARE_WIDTH;
        if (Parent()->ClientSize().x < new_lr.x - RESIZE_SQUARE_WIDTH)
            new_lr.x = Parent()->ClientSize().x + RESIZE_SQUARE_WIDTH;
        if (Parent()->ClientSize().y < new_lr.y - RESIZE_SQUARE_WIDTH)
            new_lr.y = Parent()->ClientSize().y + RESIZE_SQUARE_WIDTH;
        SizeMove(new_ul, new_lr);
    }
}

void Resizer::LButtonUp(const GG::Pt& pt, Uint32 keys)
{
    if (m_original_ul != RelativeUpperLeft() || m_original_size != Size())
        DraggedOrResizedSignal("");
    m_handle_being_dragged = HANDLE_NONE;
}

void Resizer::LClick(const GG::Pt& pt, Uint32 keys)
{
    if (m_handle_being_dragged == HANDLE_CLIENT_WND && m_original_ul == RelativeUpperLeft()) {
        m_handle_being_dragged = HANDLE_NONE;
        if (keys & GG::GGKMOD_CTRL)
            DeselectedSignal(this);
        else
            ReselectedSignal(this);
    } else {
        if (m_original_ul != RelativeUpperLeft() || m_original_size != Size())
            DraggedOrResizedSignal("");
        m_handle_being_dragged = HANDLE_NONE;
    }
}

void Resizer::SizeMove(const GG::Pt& ul, const GG::Pt& lr)
{
    bool non_resize_drag = (lr.x - ul.x) == Width() && (lr.y - ul.y) == Height();
    GG::Pt old_ul = RelativeUpperLeft();
    GG::Pt wnd_ul = m_wnd->RelativeUpperLeft() + (ul - RelativeUpperLeft());
    GG::Pt wnd_lr = m_wnd->RelativeLowerRight() + (lr - RelativeLowerRight());
    m_wnd->SizeMove(wnd_ul, wnd_lr);
    Wnd::SizeMove(WndUpperLeft() - Parent()->ClientUpperLeft() - GG::Pt(RESIZE_SQUARE_WIDTH, RESIZE_SQUARE_WIDTH),
                  WndLowerRight() - Parent()->ClientUpperLeft() + GG::Pt(RESIZE_SQUARE_WIDTH, RESIZE_SQUARE_WIDTH));
    if (non_resize_drag && !s_dragging_other_resizers) {
        s_dragging_other_resizers = true;
        GG::Pt drag_movement = RelativeUpperLeft() - old_ul;
        for (std::set<Resizer*>::iterator it = s_all_resizers.begin(); it != s_all_resizers.end(); ++it) {
            if ((*it) != this)
                (*it)->OffsetMove(drag_movement);
        }
        s_dragging_other_resizers = false;
    }
}

GG::Rect Resizer::HandleRect(Handle handle)
{
    assert(handle != HANDLE_NONE);
    GG::Pt ul = RelativeUpperLeft(), lr = RelativeLowerRight();
    int left_of_horizontal_midpoint = ((lr.x + ul.x) - RESIZE_SQUARE_WIDTH) / 2;
    int top_of_vertical_midpoint = ((lr.y + ul.y) - RESIZE_SQUARE_WIDTH) / 2;
    switch (handle) {
    case HANDLE_CLIENT_WND:
        return GG::Rect(ul.x + RESIZE_SQUARE_WIDTH, ul.y + RESIZE_SQUARE_WIDTH,
                        lr.x - RESIZE_SQUARE_WIDTH, lr.y - RESIZE_SQUARE_WIDTH);
    case HANDLE_UL:
        return GG::Rect(ul.x, ul.y,
                        ul.x + RESIZE_SQUARE_WIDTH, ul.y + RESIZE_SQUARE_WIDTH);
    case HANDLE_TOP:
        return GG::Rect(left_of_horizontal_midpoint, ul.y,
                        left_of_horizontal_midpoint + RESIZE_SQUARE_WIDTH, ul.y + RESIZE_SQUARE_WIDTH);
    case HANDLE_UR:
        return GG::Rect(lr.x - RESIZE_SQUARE_WIDTH, ul.y,
                        lr.x, ul.y + RESIZE_SQUARE_WIDTH);
    case HANDLE_LEFT:
        return GG::Rect(ul.x, top_of_vertical_midpoint,
                        ul.x + RESIZE_SQUARE_WIDTH, top_of_vertical_midpoint + RESIZE_SQUARE_WIDTH);
    case HANDLE_RIGHT:
        return GG::Rect(lr.x - RESIZE_SQUARE_WIDTH, top_of_vertical_midpoint,
                        lr.x, top_of_vertical_midpoint + RESIZE_SQUARE_WIDTH);
    case HANDLE_LL:
        return GG::Rect(ul.x, lr.y - RESIZE_SQUARE_WIDTH,
                        ul.x + RESIZE_SQUARE_WIDTH, lr.y);
    case HANDLE_BOTTOM:
        return GG::Rect(left_of_horizontal_midpoint, lr.y - RESIZE_SQUARE_WIDTH,
                        left_of_horizontal_midpoint + RESIZE_SQUARE_WIDTH, lr.y);
    case HANDLE_LR:
        return GG::Rect(lr.x - RESIZE_SQUARE_WIDTH, lr.y - RESIZE_SQUARE_WIDTH,
                        lr.x, lr.y);
    default:
        assert(0);
        return GG::Rect();
    }
}

GG::Pt Resizer::WndUpperLeft() const
{
    return UpperLeftOf(m_wnd, Parent());
}

GG::Pt Resizer::WndLowerRight() const
{
    return m_wnd->LowerRight() +
        (GG::MatchesOrContains(Parent(), m_wnd) ? GG::Pt() : Parent()->ClientUpperLeft());
}

bool Resizer::OnlyActiveResizer()
{
    return s_all_resizers.size() == 1 && *s_all_resizers.begin() == this;
}
