#include "util.h"

using namespace empathy;
using namespace empathy::util;

namespace empathy::util::colors
{
    const char* base00 = "181818";
    const char* base01 = "282828";
    const char* base02 = "383838";
    const char* base03 = "585858";
    const char* base04 = "b8b8b8";
    const char* base05 = "d8d8d8";
    const char* base06 = "e8e8e8";
    const char* base07 = "f8f8f8";
    const char* base08 = "ab4642";
    const char* base09 = "dc9656";
    const char* base0A = "f7ca88";
    const char* base0B = "a1b56c";
    const char* base0C = "86c1b9";
    const char* base0D = "7cafc2";
    const char* base0E = "ba8baf";
    const char* base0F = "a16946";
}

namespace
{
    const char* accent_colors[] =
    {
        colors::base08,
        colors::base09,
        colors::base0A,
        colors::base0B,
        colors::base0C,
        colors::base0D,
        colors::base0E,
        colors::base0F
    };
}

namespace empathy::util
{
    const char* GraphVizBuilder::BGColor = colors::base00;
    const char* GraphVizBuilder::TextColor = colors::base05;

    GraphVizBuilder::GraphVizBuilder( ostream& output, bool vertical ) :
        m_output( output )
    {
        m_output <<
            "digraph \"empathy\"\n"
            "{\n"
            "\tbgcolor=\"#" << BGColor << "\";\n"
            "\trankdir=" << ( vertical ? "TB" : "LR" ) << ";\n"
            "\tnode [ shape=none ];\n"
            "\tedge [ color=\"#" << TextColor << "\" ];\n";

    }

    GraphVizBuilder::~GraphVizBuilder()
    {
        while( !m_workQueue.empty() )
        {
            m_workQueue.front()();
            m_workQueue.pop();
        }

        m_output << "\n}\n";
    }

    ostream& GraphVizBuilder::indent() const
    {
        for( auto i = 0; i < m_indention; ++i )
            m_output << '\t';

        return m_output;
    }

    ostream& GraphVizBuilder::newLine() const
    {
        m_output << '\n';
        return indent();
    }

    const char* GraphVizBuilder::GetNodeColor( uint32_t id )
    {
        return accent_colors[ id % ( sizeof( accent_colors ) / sizeof( const char* ) ) ];
    }

    uint32_t GraphVizBuilder::getNodeId( const void* pNodeAddress )
    {
        auto it = m_nodesDict.find( pNodeAddress );
        if( it != m_nodesDict.end() )
            return it->second;

        auto id = m_nodeCount++;
        m_nodesDict.emplace( pNodeAddress, id );
        return id;
    }

    bool GraphVizBuilder::wasNodeGenerated( const void* pNodeAddress ) const
    {
        return m_generatedNodes.find( pNodeAddress ) != m_generatedNodes.end();
    }

    void GraphVizBuilder::addEdge( uint32_t fromId, uint32_t toId )
    {
        auto portId = m_portCount - 1;

        queueWork( [=]
        {
            newLine()
                << "object" << fromId << ':' << portId
                << " -> object" << toId
                << "[ color=\"#" << GetNodeColor( toId ) << "\" ]";
        } );
    }

    GraphVizBuilder::Indention::Indention( GraphVizBuilder& builder ) :
        m_builder( builder )
    {
        ++m_builder.m_indention;
    }

    GraphVizBuilder::Indention::~Indention()
    {
        --m_builder.m_indention;
    }

    GraphVizBuilder::Table::Table( GraphVizBuilder& builder, const char* pColor ) :
        m_builder( builder )
    {
        m_builder.newLine() <<
            "<table border=\"0\" cellborder=\"1\" cellspacing=\"0\""
            " color=\"#" << pColor << "\">";

        ++m_builder.m_indention;
    }

    GraphVizBuilder::Table::~Table()
    {
        --m_builder.m_indention;
        m_builder.newLine() << "</table>";
    }

    GraphVizBuilder::Row::Row( GraphVizBuilder& builder ) :
        m_builder( builder )
    {
        m_builder.newLine() << "<tr>";
        ++m_builder.m_indention;
    }

    GraphVizBuilder::Row::~Row()
    {
        --m_builder.m_indention;
        m_builder.newLine() << "</tr>";
    }

    GraphVizBuilder::Cell::Cell( GraphVizBuilder& builder ) :
        m_builder( builder )
    {
        m_builder.newLine() << "<td port=\"" << m_builder.m_portCount++ << "\">";
        ++m_builder.m_indention;
    }

    GraphVizBuilder::Cell::~Cell()
    {
        --m_builder.m_indention;
        m_builder.newLine() << "</td>";
    }

    GraphVizBuilder::Color::Color( GraphVizBuilder& builder, const char* pColor ) :
        m_builder( builder )
    {
        m_builder.newLine()
            << "<font face=\"Noto Sans\" point-size=\"12\" color=\"#" << pColor << "\">";

        ++m_builder.m_indention;
    }

    GraphVizBuilder::Color::~Color()
    {
        --m_builder.m_indention;
        m_builder.newLine() << "</font>";
    }

    GraphVizBuilder::Node::Node( GraphVizBuilder& builder, const void* pAddress, const char* pTitle ) :
        m_builder( builder )
    {
        m_builder.m_generatedNodes.insert( pAddress );
        m_builder.m_currentNode = m_builder.getNodeId( pAddress );

        m_builder.newLine() << "object" << m_builder.m_currentNode;
        m_builder.newLine() << "[ label=<";

        ++m_builder.m_indention;

        auto color = GetNodeColor( m_builder.m_currentNode ) ;

        m_builder.newLine() <<
            "<table border=\"0\" cellborder=\"1\" cellspacing=\"0\""
            " color=\"#" << color << "\">";

        ++m_builder.m_indention;

        if( pTitle )
        {
            Row row( m_builder );
            Cell cell( m_builder );
            Color c( m_builder, color );

            m_builder.m_output << m_builder.m_currentNode << ": " << pTitle;
        }
    }

    GraphVizBuilder::Node::~Node()
    {
        --m_builder.m_indention;
        m_builder.newLine() << "</table>";

        --m_builder.m_indention;
        m_builder.newLine() << "> ]";
    }
}
