/*
 * Patchbay Canvas engine using QGraphicsView/Scene
 * Copyright (C) 2010-2012 Filipe Coelho <falktx@falktx.com>
 *
 * This program 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 2 of the License, or
 * any later version.
 *
 * This program 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.
 *
 * For a full copy of the GNU General Public License see the COPYING file
 */

#include "canvasbox.h"

#include <QtCore/QTimer>
#include <QtGui/QCursor>
#include <QtGui/QInputDialog>
#include <QtGui/QMenu>
#include <QtGui/QGraphicsSceneContextMenuEvent>
#include <QtGui/QGraphicsSceneMouseEvent>
#include <QtGui/QPainter>

#include "canvasline.h"
#include "canvasbezierline.h"
#include "canvasport.h"
#include "canvasboxshadow.h"
#include "canvasicon.h"

START_NAMESPACE_PATCHCANVAS

CanvasBox::CanvasBox(int group_id, QString group_name, Icon icon, QGraphicsItem* parent) :
    QGraphicsItem(parent, canvas.scene)
{
    // Save Variables, useful for later
    m_group_id   = group_id;
    m_group_name = group_name;

    // Base Variables
    p_width  = 50;
    p_height = 25;

    m_last_pos = QPointF();
    m_splitted = false;
    m_splitted_mode = PORT_MODE_NULL;

    m_cursor_moving = false;
    m_forced_split  = false;
    m_mouse_down    = false;

    m_port_list_ids.clear();
    m_connection_lines.clear();

    // Set Font
    m_font_name = QFont(canvas.theme->box_font_name, canvas.theme->box_font_size, canvas.theme->box_font_state);
    m_font_port = QFont(canvas.theme->port_font_name, canvas.theme->port_font_size, canvas.theme->port_font_state);

    // Icon
    icon_svg = new CanvasIcon(icon, group_name, this);

    // Shadow
    if (options.eyecandy)
    {
        shadow = new CanvasBoxShadow(toGraphicsObject());
        shadow->setFakeParent(this);
        setGraphicsEffect(shadow);
    }
    else
        shadow = 0;

    // Final touches
    setFlags(QGraphicsItem::ItemIsMovable|QGraphicsItem::ItemIsSelectable);

    // Wait for at least 1 port
    if (options.auto_hide_groups)
        setVisible(false);

    updatePositions();
}

CanvasBox::~CanvasBox()
{
    if (shadow)
        delete shadow;
    delete icon_svg;
}

int CanvasBox::getGroupId()
{
    return m_group_id;
}

QString CanvasBox::getGroupName()
{
    return m_group_name;
}

bool CanvasBox::isSplitted()
{
    return m_splitted;
}

PortMode CanvasBox::getSplittedMode()
{
    return m_splitted_mode;
}

int CanvasBox::getPortCount()
{
    return m_port_list_ids.count();
}

QList<int> CanvasBox::getPortList()
{
    return m_port_list_ids;
}

void CanvasBox::setIcon(Icon icon)
{
    icon_svg->setIcon(icon, m_group_name);
}

void CanvasBox::setSplit(bool split, PortMode mode)
{
    m_splitted = split;
    m_splitted_mode = mode;
}

void CanvasBox::setGroupName(QString group_name)
{
    m_group_name = group_name;
    updatePositions();
}

void CanvasBox::setShadowOpacity(float opacity)
{
    if (shadow)
        shadow->setOpacity(opacity);
}

CanvasPort* CanvasBox::addPortFromGroup(int port_id, QString port_name, PortMode port_mode, PortType port_type)
{
    if (m_port_list_ids.count() == 0)
    {
        if (options.auto_hide_groups)
        {
            if (options.eyecandy == EYECANDY_FULL)
                CanvasItemFX(this, true);
            setVisible(true);
        }
    }

    CanvasPort* new_widget = new CanvasPort(port_id, port_name, port_mode, port_type, this);

    port_dict_t port_dict;
    port_dict.group_id  = m_group_id;
    port_dict.port_id   = port_id;
    port_dict.port_name = port_name;
    port_dict.port_mode = port_mode;
    port_dict.port_type = port_type;
    port_dict.widget    = new_widget;

    m_port_list_ids.append(port_id);

    return new_widget;
}

void CanvasBox::removePortFromGroup(int port_id)
{
    if (m_port_list_ids.contains(port_id))
    {
        m_port_list_ids.removeOne(port_id);
    }
    else
    {
        qCritical("PatchCanvas::CanvasBox->removePort(%i) - unable to find port to remove", port_id);
        return;
    }

    if (m_port_list_ids.count() > 0)
    {
        updatePositions();
    }
    else if (isVisible())
    {
        if (options.auto_hide_groups)
        {
            if (options.eyecandy == EYECANDY_FULL)
                CanvasItemFX(this, false);
            else
                setVisible(false);
        }
    }
}

void CanvasBox::addLineFromGroup(AbstractCanvasLine* line, int connection_id)
{
    cb_line_t new_cbline;
    new_cbline.line = line;
    new_cbline.connection_id = connection_id;
    m_connection_lines.append(new_cbline);
}

void CanvasBox::removeLineFromGroup(int connection_id)
{
    foreach2 (const cb_line_t& connection, m_connection_lines)
        if (connection.connection_id == connection_id)
        {
            m_connection_lines.takeAt(i);
            return;
        }
    }

    qCritical("PatchCanvas::CanvasBox->removeLineFromGroup(%i) - unable to find line to remove", connection_id);
}

void CanvasBox::checkItemPos()
{
    if (canvas.size_rect.isNull() == false)
    {
        QPointF pos = scenePos();
        if (canvas.size_rect.contains(pos) == false || canvas.size_rect.contains(pos+QPointF(p_width, p_height)) == false)
        {
            if (pos.x() < canvas.size_rect.x())
                setPos(canvas.size_rect.x(), pos.y());
            else if (pos.x()+p_width > canvas.size_rect.width())
                setPos(canvas.size_rect.width()-p_width, pos.y());

            pos = scenePos();
            if (pos.y() < canvas.size_rect.y())
                setPos(pos.x(), canvas.size_rect.y());
            else if (pos.y()+p_height > canvas.size_rect.height())
                setPos(pos.x(), canvas.size_rect.height()-p_height);
        }
    }
}

void CanvasBox::removeIconFromScene()
{
    canvas.scene->removeItem(icon_svg);
}

void CanvasBox::updatePositions()
{
    prepareGeometryChange();

    int max_in_width   = 0;
    int max_in_height  = 24;
    int max_out_width  = 0;
    int max_out_height = 24;
    bool have_audio_jack_in, have_audio_jack_out, have_midi_jack_in, have_midi_jack_out;
    bool have_midi_a2j_in,  have_midi_a2j_out, have_midi_alsa_in,  have_midi_alsa_out;
    have_audio_jack_in  = have_midi_jack_in  = have_midi_a2j_in  = have_midi_alsa_in  = false;
    have_audio_jack_out = have_midi_jack_out = have_midi_a2j_out = have_midi_alsa_out = false;

    // reset box size
    p_width  = 50;
    p_height = 25;

    // Check Text Name size
    int app_name_size = QFontMetrics(m_font_name).width(m_group_name)+30;
    if (app_name_size > p_width)
        p_width = app_name_size;

    // Get Port List
    QList<port_dict_t> port_list;
    foreach (const port_dict_t& port, canvas.port_list)
    {
        if (m_port_list_ids.contains(port.port_id))
            port_list.append(port);
    }

    // Get Max Box Width/Height
    foreach (const port_dict_t& port, port_list)
    {
        if (port.port_mode == PORT_MODE_INPUT)
        {
            max_in_height += 18;

            int size = QFontMetrics(m_font_port).width(port.port_name);
            if (size > max_in_width)
                max_in_width = size;

            if (port.port_type == PORT_TYPE_AUDIO_JACK && have_audio_jack_in == false)
            {
                have_audio_jack_in = true;
                max_in_height += 2;
            }
            else if (port.port_type == PORT_TYPE_MIDI_JACK && have_midi_jack_in == false)
            {
                have_midi_jack_in = true;
                max_in_height += 2;
            }
            else if (port.port_type == PORT_TYPE_MIDI_A2J && have_midi_a2j_in == false)
            {
                have_midi_a2j_in = true;
                max_in_height += 2;
            }
            else if (port.port_type == PORT_TYPE_MIDI_ALSA && have_midi_alsa_in == false)
            {
                have_midi_alsa_in = true;
                max_in_height += 2;
            }
        }
        else if (port.port_mode == PORT_MODE_OUTPUT)
        {
            max_out_height += 18;

            int size = QFontMetrics(m_font_port).width(port.port_name);
            if (size > max_out_width)
                max_out_width = size;

            if (port.port_type == PORT_TYPE_AUDIO_JACK && have_audio_jack_out == false)
            {
                have_audio_jack_out = true;
                max_out_height += 2;
            }
            else if (port.port_type == PORT_TYPE_MIDI_JACK && have_midi_jack_out == false)
            {
                have_midi_jack_out = true;
                max_out_height += 2;
            }
            else if (port.port_type == PORT_TYPE_MIDI_A2J && have_midi_a2j_out == false)
            {
                have_midi_a2j_out = true;
                max_out_height += 2;
            }
            else if (port.port_type == PORT_TYPE_MIDI_ALSA && have_midi_alsa_out == false)
            {
                have_midi_alsa_out = true;
                max_out_height += 2;
            }
        }
    }

    int final_width = 30 + max_in_width + max_out_width;
    if (final_width > p_width)
        p_width = final_width;

    if (max_in_height > p_height)
        p_height = max_in_height;

    if (max_out_height > p_height)
        p_height = max_out_height;

    // Remove bottom space
    p_height -= 2;

    int last_in_pos  = 24;
    int last_out_pos = 24;
    PortType last_in_type  = PORT_TYPE_NULL;
    PortType last_out_type = PORT_TYPE_NULL;

    // Re-position ports, AUDIO_JACK
    foreach (const port_dict_t& port, port_list)
    {
        if (port.port_type == PORT_TYPE_AUDIO_JACK)
        {
            if (port.port_mode == PORT_MODE_INPUT)
            {
                port.widget->setPos(QPointF(1, last_in_pos));
                port.widget->setPortWidth(max_in_width);

                last_in_pos += 18;
                last_in_type = port.port_type;
            }
            else if (port.port_mode == PORT_MODE_OUTPUT)
            {
                port.widget->setPos(QPointF(p_width-max_out_width-13, last_out_pos));
                port.widget->setPortWidth(max_out_width);

                last_out_pos += 18;
                last_out_type = port.port_type;
            }
        }
    }

    // Re-position ports, MIDI_JACK
    foreach (const port_dict_t& port, port_list)
    {
        if (port.port_type == PORT_TYPE_MIDI_JACK)
        {
            if (port.port_mode == PORT_MODE_INPUT)
            {
                if (last_in_type != PORT_TYPE_NULL && port.port_type != last_in_type)
                    last_in_pos += 2;

                port.widget->setPos(QPointF(1, last_in_pos));
                port.widget->setPortWidth(max_in_width);

                last_in_pos += 18;
                last_in_type = port.port_type;
            }
            else if (port.port_mode == PORT_MODE_OUTPUT)
            {
                if (last_out_type != PORT_TYPE_NULL && port.port_type != last_out_type)
                    last_out_pos += 2;

                port.widget->setPos(QPointF(p_width-max_out_width-13, last_out_pos));
                port.widget->setPortWidth(max_out_width);

                last_out_pos += 18;
                last_out_type = port.port_type;
            }
        }
    }

    // Re-position ports, MIDI_A2J
    foreach (const port_dict_t& port, port_list)
    {
        if (port.port_type == PORT_TYPE_MIDI_A2J)
        {
            if (port.port_mode == PORT_MODE_INPUT)
            {
                if (last_in_type != PORT_TYPE_NULL && port.port_type != last_in_type)
                    last_in_pos += 2;

                port.widget->setPos(QPointF(1, last_in_pos));
                port.widget->setPortWidth(max_in_width);

                last_in_pos += 18;
                last_in_type = port.port_type;
            }
            else if (port.port_mode == PORT_MODE_OUTPUT)
            {
                if (last_out_type != PORT_TYPE_NULL && port.port_type != last_out_type)
                    last_out_pos += 2;

                port.widget->setPos(QPointF(p_width-max_out_width-13, last_out_pos));
                port.widget->setPortWidth(max_out_width);

                last_out_pos += 18;
                last_out_type = port.port_type;
            }
        }
    }

    // Re-position ports, MIDI_ALSA
    foreach (const port_dict_t& port, port_list)
    {
        if (port.port_type == PORT_TYPE_MIDI_ALSA)
        {
            if (port.port_mode == PORT_MODE_INPUT)
            {
                if (last_in_type != PORT_TYPE_NULL && port.port_type != last_in_type)
                    last_in_pos += 2;

                port.widget->setPos(QPointF(1, last_in_pos));
                port.widget->setPortWidth(max_in_width);

                last_in_pos += 18;
                last_in_type = port.port_type;
            }
            else if (port.port_mode == PORT_MODE_OUTPUT)
            {
                if (last_out_type != PORT_TYPE_NULL && port.port_type != last_out_type)
                    last_out_pos += 2;

                port.widget->setPos(QPointF(p_width-max_out_width-13, last_out_pos));
                port.widget->setPortWidth(max_out_width);

                last_out_pos += 18;
                last_out_type = port.port_type;
            }
        }
    }

    repaintLines(true);
    update();
}

void CanvasBox::repaintLines(bool forced)
{
    if (pos() != m_last_pos || forced)
    {
        foreach (const cb_line_t& connection, m_connection_lines)
            connection.line->updateLinePos();
    }

    m_last_pos = pos();
}

void CanvasBox::resetLinesZValue()
{
    foreach (const connection_dict_t& connection, canvas.connection_list)
    {
        int z_value;
        if (m_port_list_ids.contains(connection.port_out_id) && m_port_list_ids.contains(connection.port_in_id))
            z_value = canvas.last_z_value;
        else
            z_value = canvas.last_z_value-1;

        connection.widget->setZValue(z_value);
    }
}

int CanvasBox::type() const
{
    return CanvasBoxType;
}

void CanvasBox::contextMenuEvent(QGraphicsSceneContextMenuEvent* event)
{
    QMenu menu;
    QMenu discMenu("Disconnect", &menu);

    QList<int> port_con_list;
    QList<int> port_con_list_ids;

    foreach (const int& port_id, m_port_list_ids)
    {
        QList<int> tmp_port_con_list = CanvasGetPortConnectionList(port_id);
        foreach (const int& port_con_id, tmp_port_con_list)
        {
            if (port_con_list.contains(port_con_id) == false)
            {
                port_con_list.append(port_con_id);
                port_con_list_ids.append(port_id);
            }
        }
    }

    if (port_con_list.count() > 0)
    {
        for (int i=0; i < port_con_list.count(); i++)
        {
            int port_con_id = CanvasGetConnectedPort(port_con_list[i], port_con_list_ids[i]);
            QAction* act_x_disc = discMenu.addAction(CanvasGetFullPortName(port_con_id));
            act_x_disc->setData(port_con_list[i]);
            QObject::connect(act_x_disc, SIGNAL(triggered()), canvas.qobject, SLOT(PortContextMenuDisconnect()));
        }
    }
    else
    {
        QAction* act_x_disc = discMenu.addAction("No connections");
        act_x_disc->setEnabled(false);
    }

    menu.addMenu(&discMenu);
    QAction* act_x_disc_all   = menu.addAction("Disconnect &All");
    QAction* act_x_sep1       = menu.addSeparator();
    QAction* act_x_info       = menu.addAction("&Info");
    QAction* act_x_rename     = menu.addAction("&Rename");
    QAction* act_x_sep2       = menu.addSeparator();
    QAction* act_x_split_join = menu.addAction(m_splitted ? "Join" : "Split");

    if (features.group_info == false)
        act_x_info->setVisible(false);

    if (features.group_rename == false)
        act_x_rename->setVisible(false);

    if (features.group_info == false && features.group_rename == false)
        act_x_sep1->setVisible(false);

    bool haveIns, haveOuts;
    haveIns = haveOuts = false;
    foreach (const port_dict_t& port, canvas.port_list)
    {
        if (m_port_list_ids.contains(port.port_id))
        {
            if (port.port_mode == PORT_MODE_INPUT)
                haveIns = true;
            else if (port.port_mode == PORT_MODE_OUTPUT)
                haveOuts = true;
        }
    }

    if (m_splitted == false && (haveIns && haveOuts) == false)
    {
        act_x_sep2->setVisible(false);
        act_x_split_join->setVisible(false);
    }

    QAction* act_selected = menu.exec(event->screenPos());

    if (act_selected == act_x_disc_all)
    {
        foreach (const int& port_id, port_con_list)
            canvas.callback(ACTION_PORTS_DISCONNECT, port_id, 0, "");
    }
    else if (act_selected == act_x_info)
    {
        canvas.callback(ACTION_GROUP_INFO, m_group_id, 0, "");
    }
    else if (act_selected == act_x_rename)
    {
        bool ok_check;
        QString new_name = QInputDialog::getText(0, "Rename Group", "New name:", QLineEdit::Normal, m_group_name, &ok_check);
        if (ok_check and !new_name.isEmpty())
        {
            canvas.callback(ACTION_GROUP_RENAME, m_group_id, 0, new_name);
        }
    }
    else if (act_selected == act_x_split_join)
    {
        if (m_splitted)
            canvas.callback(ACTION_GROUP_JOIN, m_group_id, 0, "");
        else
            canvas.callback(ACTION_GROUP_SPLIT, m_group_id, 0, "");

    }

    event->accept();
}

void CanvasBox::mousePressEvent(QGraphicsSceneMouseEvent* event)
{
    canvas.last_z_value += 1;
    setZValue(canvas.last_z_value);
    resetLinesZValue();
    m_cursor_moving = false;

    if (event->button() == Qt::RightButton)
    {
        canvas.scene->clearSelection();
        setSelected(true);
        m_mouse_down = false;
        return event->accept();
    }
    else if (event->button() == Qt::LeftButton)
    {
        if (sceneBoundingRect().contains(event->scenePos()))
            m_mouse_down = true;
        else
        {
            // Fixes a weird Qt behaviour with right-click mouseMove
            m_mouse_down = false;
            return event->ignore();
        }
    }
    else
        m_mouse_down = false;

    QGraphicsItem::mousePressEvent(event);
}

void CanvasBox::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
{
    if (m_mouse_down)
    {
        if (m_cursor_moving == false)
        {
            setCursor(QCursor(Qt::SizeAllCursor));
            m_cursor_moving = true;
        }
        repaintLines();
    }
    QGraphicsItem::mouseMoveEvent(event);
}

void CanvasBox::mouseReleaseEvent(QGraphicsSceneMouseEvent* event)
{
    if (m_cursor_moving)
        setCursor(QCursor(Qt::ArrowCursor));
    m_mouse_down = false;
    m_cursor_moving = false;
    QGraphicsItem::mouseReleaseEvent(event);
}

QRectF CanvasBox::boundingRect() const
{
    return QRectF(0, 0, p_width, p_height);
}

void CanvasBox::paint(QPainter* painter, const QStyleOptionGraphicsItem* /*option*/, QWidget* /*widget*/)
{
    painter->setRenderHint(QPainter::Antialiasing, false);

    if (isSelected())
        painter->setPen(canvas.theme->box_pen_sel);
    else
        painter->setPen(canvas.theme->box_pen);

    QLinearGradient box_gradient(0, 0, 0, p_height);
    box_gradient.setColorAt(0, canvas.theme->box_bg_1);
    box_gradient.setColorAt(1, canvas.theme->box_bg_2);

    painter->setBrush(box_gradient);
    painter->drawRect(0, 0, p_width, p_height);

    QPointF text_pos(25, 16);

    painter->setFont(m_font_name);
    painter->setPen(canvas.theme->box_text);
    painter->drawText(text_pos, m_group_name);

    repaintLines();
}

END_NAMESPACE_PATCHCANVAS
