#include "cellarrow.hpp"

#include <osg/Array>
#include <osg/GL>
#include <osg/Geometry>
#include <osg/Group>
#include <osg/Math>
#include <osg/PositionAttitudeTransform>
#include <osg/PrimitiveSet>
#include <osg/Quat>
#include <osg/StateAttribute>
#include <osg/StateSet>
#include <osg/Vec3f>
#include <osg/Vec4f>

#include "../../model/prefs/state.hpp"

#include <apps/opencs/model/prefs/shortcutmanager.hpp>
#include <apps/opencs/model/world/cellcoordinates.hpp>
#include <apps/opencs/view/render/tagbase.hpp>

#include <components/misc/constants.hpp>

#include "mask.hpp"

namespace CSVRender
{
    struct WorldspaceHitResult;
}

CSVRender::CellArrowTag::CellArrowTag(CellArrow* arrow)
    : TagBase(Mask_CellArrow)
    , mArrow(arrow)
{
}

CSVRender::CellArrow* CSVRender::CellArrowTag::getCellArrow() const
{
    return mArrow;
}

QString CSVRender::CellArrowTag::getToolTip(bool hideBasics, const WorldspaceHitResult& /*hit*/) const
{
    QString text("Direction: ");

    switch (mArrow->getDirection())
    {
        case CellArrow::Direction_North:
            text += "North";
            break;
        case CellArrow::Direction_West:
            text += "West";
            break;
        case CellArrow::Direction_South:
            text += "South";
            break;
        case CellArrow::Direction_East:
            text += "East";
            break;
    }

    if (!hideBasics)
    {
        text +=
            "<p>"
            "Modify which cells are shown"
            "<ul><li>{scene-edit-primary}: Add cell in given direction</li>"
            "<li>{scene-edit-secondary}: Add cell and remove old cell</li>"
            "<li>{scene-select-primary}: Add cells in given direction</li>"
            "<li>{scene-select-secondary}: Add cells and remove old cells</li>"
            "<li>{scene-load-cam-cell}: Load cell where camera is located</li>"
            "<li>{scene-load-cam-eastcell}: Load cell to east</li>"
            "<li>{scene-load-cam-northcell}: Load cell to north</li>"
            "<li>{scene-load-cam-westcell}: Load cell to west</li>"
            "<li>{scene-load-cam-southcell}: Load cell to south</li>"
            "</ul>";
    }

    return CSMPrefs::State::get().getShortcutManager().processToolTip(text);
}

void CSVRender::CellArrow::adjustTransform()
{
    // position
    const int cellSize = Constants::CellSizeInUnits;
    const int offset = cellSize / 2 + 600;

    int x = mCoordinates.getX() * cellSize + cellSize / 2;
    int y = mCoordinates.getY() * cellSize + cellSize / 2;

    float xr = 0;
    float yr = 0;
    float zr = 0;

    float angle = osg::DegreesToRadians(90.0f);

    switch (mDirection)
    {
        case Direction_North:
            y += offset;
            xr = -angle;
            zr = angle;
            break;
        case Direction_West:
            x -= offset;
            yr = -angle;
            break;
        case Direction_South:
            y -= offset;
            xr = angle;
            zr = angle;
            break;
        case Direction_East:
            x += offset;
            yr = angle;
            break;
    };

    mBaseNode->setPosition(osg::Vec3f(x, y, 0));

    // orientation
    osg::Quat xr2(xr, osg::Vec3f(1, 0, 0));
    osg::Quat yr2(yr, osg::Vec3f(0, 1, 0));
    osg::Quat zr2(zr, osg::Vec3f(0, 0, 1));
    mBaseNode->setAttitude(zr2 * yr2 * xr2);
}

void CSVRender::CellArrow::buildShape()
{
    osg::ref_ptr<osg::Geometry> geometry(new osg::Geometry);

    const int arrowWidth = 2700;
    const int arrowLength = 1350;
    const int arrowHeight = 300;

    osg::Vec3Array* vertices = new osg::Vec3Array;
    for (int i2 = 0; i2 < 2; ++i2)
        for (int i = 0; i < 2; ++i)
        {
            float height = i ? -arrowHeight / 2 : arrowHeight / 2;
            vertices->push_back(osg::Vec3f(height, -arrowWidth / 2, 0));
            vertices->push_back(osg::Vec3f(height, arrowWidth / 2, 0));
            vertices->push_back(osg::Vec3f(height, 0, arrowLength));
        }

    geometry->setVertexArray(vertices);

    osg::DrawElementsUShort* primitives = new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLES, 0);

    // top
    primitives->push_back(0);
    primitives->push_back(1);
    primitives->push_back(2);

    // bottom
    primitives->push_back(5);
    primitives->push_back(4);
    primitives->push_back(3);

    // back
    primitives->push_back(3 + 6);
    primitives->push_back(4 + 6);
    primitives->push_back(1 + 6);

    primitives->push_back(3 + 6);
    primitives->push_back(1 + 6);
    primitives->push_back(0 + 6);

    // sides
    primitives->push_back(0 + 6);
    primitives->push_back(2 + 6);
    primitives->push_back(5 + 6);

    primitives->push_back(0 + 6);
    primitives->push_back(5 + 6);
    primitives->push_back(3 + 6);

    primitives->push_back(4 + 6);
    primitives->push_back(5 + 6);
    primitives->push_back(2 + 6);

    primitives->push_back(4 + 6);
    primitives->push_back(2 + 6);
    primitives->push_back(1 + 6);

    geometry->addPrimitiveSet(primitives);

    osg::Vec4Array* colours = new osg::Vec4Array;

    for (int i = 0; i < 6; ++i)
        colours->push_back(osg::Vec4f(0.11f, 0.6f, 0.95f, 1.0f));
    for (int i = 0; i < 6; ++i)
        colours->push_back(osg::Vec4f(0.08f, 0.44f, 0.7f, 1.0f));

    geometry->setColorArray(colours, osg::Array::BIND_PER_VERTEX);

    geometry->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF);

    mBaseNode->addChild(geometry);
}

CSVRender::CellArrow::CellArrow(osg::Group* cellNode, Direction direction, const CSMWorld::CellCoordinates& coordinates)
    : mDirection(direction)
    , mParentNode(cellNode)
    , mCoordinates(coordinates)
{
    mBaseNode = new osg::PositionAttitudeTransform;

    mBaseNode->setUserData(new CellArrowTag(this));

    mParentNode->addChild(mBaseNode);

    mBaseNode->setNodeMask(Mask_CellArrow);

    adjustTransform();
    buildShape();
}

CSVRender::CellArrow::~CellArrow()
{
    mParentNode->removeChild(mBaseNode);
}

CSMWorld::CellCoordinates CSVRender::CellArrow::getCoordinates() const
{
    return mCoordinates;
}

CSVRender::CellArrow::Direction CSVRender::CellArrow::getDirection() const
{
    return mDirection;
}
