#include "precompiled.h"

#include <vector>
#include <string>

#include "simulation/Entity.h"

#include "TerrainProperties.h"
#include "TextureManager.h"
#include "ps/Overlay.h"

#include "ps/Parser.h"
#include "ps/XML/XeroXMB.h"
#include "ps/XML/Xeromyces.h"

#include "ps/CLogger.h"
#define LOG_CATEGORY "graphics"

using namespace std;

CTerrainProperties::CTerrainProperties(CTerrainPropertiesPtr parent):
    m_pParent(parent),
    m_BaseColor(0),
    m_HasBaseColor(false)
{
    if (m_pParent)
        m_Groups = m_pParent->m_Groups; 
}

CTerrainPropertiesPtr CTerrainProperties::FromXML(CTerrainPropertiesPtr parent, const char* path)
{
    CXeromyces XeroFile;
    if (XeroFile.Load(path) != PSRETURN_OK)
        return CTerrainPropertiesPtr();

    XMBElement root = XeroFile.GetRoot();
    CStr rootName = XeroFile.GetElementString(root.GetNodeName());

    // Check that we've got the right kind of xml document
    if (rootName != "Terrains")
    {
        LOG(ERROR,
            LOG_CATEGORY,
            "TextureManager: Loading %s: Root node is not terrains (found \"%s\")",
            path,
            rootName.c_str());
        return CTerrainPropertiesPtr();
    }
    
    #define ELMT(x) int el_##x = XeroFile.GetElementID(#x)
    #define ATTR(x) int at_##x = XeroFile.GetAttributeID(#x)
    ELMT(terrain);
    #undef ELMT
    #undef ATTR
    
    // Ignore all non-terrain nodes, loading the first terrain node and
    // returning it.
    // Really, we only expect there to be one child and it to be of the right
    // type, though.
    XMBElementList children = root.GetChildNodes();
    for (int i=0; i<children.Count; ++i)
    {
        //debug_printf("Object %d\n", i);
        XMBElement child = children.Item(i);

        if (child.GetNodeName() == el_terrain)
        {
            CTerrainPropertiesPtr ret (new CTerrainProperties(parent));
            ret->LoadXml(child, &XeroFile, path);
            return ret;
        }
        else
        {
            LOG(WARNING, LOG_CATEGORY, 
                "TerrainProperties: Loading %s: Unexpected node %s\n",
                path,
                XeroFile.GetElementString(child.GetNodeName()).c_str());
            // Keep reading - typos shouldn't be showstoppers
        }
    }
    
    return CTerrainPropertiesPtr();
}

void CTerrainProperties::LoadXml(XMBElement node, CXeromyces *pFile, const char *path)
{
    #define ELMT(x) int elmt_##x = pFile->GetElementID(#x)
    #define ATTR(x) int attr_##x = pFile->GetAttributeID(#x)
    ELMT(doodad);
    ELMT(passable);
    ELMT(impassable);
    ELMT(event);
    // Terrain Attribs
    ATTR(mmap);
    ATTR(groups);
    ATTR(properties);
    // Doodad Attribs
    ATTR(name);
    ATTR(max);
    // Event attribs
    ATTR(on);
    #undef ELMT
    #undef ATTR

    // stomp on "unused" warnings
    UNUSED2(attr_name);
    UNUSED2(attr_on);
    UNUSED2(attr_max);
    UNUSED2(elmt_event);
    UNUSED2(elmt_passable);
    UNUSED2(elmt_doodad);

    XMBAttributeList attribs = node.GetAttributes();
    for (int i=0;i<attribs.Count;i++)
    {
        XMBAttribute attr = attribs.Item(i);

        if (attr.Name == attr_groups)
        {
            // Parse a comma-separated list of groups, add the new entry to
            // each of them
            CParser parser;
            CParserLine parserLine;
            parser.InputTaskType("GroupList", "<_$value_,>_$value_");
            
            if (!parserLine.ParseString(parser, (CStr)attr.Value))
                continue;
            m_Groups.clear();
            for (size_t i=0;i<parserLine.GetArgCount();i++)
            {
                std::string value;
                if (!parserLine.GetArgString(i, value))
                    continue;
                CTerrainGroup *pType = g_TexMan.FindGroup(value);
                m_Groups.push_back(pType);
            }
        }
        else if (attr.Name == attr_mmap)
        {
            CColor col;
            if (!col.ParseString((CStr)attr.Value, 255))
                continue;
            
            // m_BaseColor is BGRA
            u8 *baseColor = (u8*)&m_BaseColor;
            baseColor[0] = (u8)(col.b*255);
            baseColor[1] = (u8)(col.g*255);
            baseColor[2] = (u8)(col.r*255);
            baseColor[3] = (u8)(col.a*255);
            m_HasBaseColor = true;
        }
        else if (attr.Name == attr_properties)
        {
            // TODO Parse a list of properties and store them somewhere
        }
    }
    
    XMBElementList children = node.GetChildNodes();
    for (int i=0; i<children.Count; ++i)
    {
        XMBElement child = children.Item(i);

        if (child.GetNodeName() == elmt_passable)
        {
            ReadPassability(true, child, pFile, path);
        }
        else if (child.GetNodeName() == elmt_impassable)
        {
            ReadPassability(false, child, pFile, path);
        }
        // TODO Parse information about doodads and events and store it
    }
}

void CTerrainProperties::ReadPassability(bool passable, XMBElement node, CXeromyces *pFile, const char *UNUSED(path))
{
    #define ATTR(x) int attr_##x = pFile->GetAttributeID(#x)        
    // Passable Attribs
    ATTR(type);
    ATTR(speed);
    ATTR(effect);
    ATTR(prints);
    #undef ATTR

    STerrainPassability pass(passable);
    bool hasType = false;
    bool hasSpeed;
    XMBAttributeList attribs = node.GetAttributes();
    for (int i=0;i<attribs.Count;i++)
    {
        XMBAttribute attr = attribs.Item(i);
        
        if (attr.Name == attr_type)
        {
            // FIXME Should handle lists of types as well!
            pass.m_Type = attr.Value;
            hasType = true;
        }
        else if (attr.Name == attr_speed)
        {
            CStr val=attr.Value;
            CStr trimmedVal=val.Trim(PS_TRIM_BOTH);
            pass.m_SpeedFactor = trimmedVal.ToDouble();
            if (trimmedVal[trimmedVal.size()-1] == '%')
            {
                pass.m_SpeedFactor /= 100.0;
            }
            // FIXME speed=0 could/should be made to set the terrain impassable
            hasSpeed = true;
        }
        else if (attr.Name == attr_effect)
        {
            // TODO Parse and store list of effects
        }
        else if (attr.Name == attr_prints)
        {
            // TODO Parse and store footprint effect
        }
    }
    
    if (!hasType)
    {
        m_DefaultPassability = pass;
    }
    else
    {   
        m_Passabilities.push_back(pass);
    }
}

bool CTerrainProperties::HasBaseColor()
{
    return m_HasBaseColor || (m_pParent && m_pParent->HasBaseColor());
}

u32 CTerrainProperties::GetBaseColor()
{
    if (m_HasBaseColor || !m_pParent)
        return m_BaseColor;
    else if (m_pParent)
        return m_pParent->GetBaseColor();
    else
        // White, full opacity.. but this value shouldn't ever be used
        return 0xFFFFFFFF;
}

const STerrainPassability &CTerrainProperties::GetPassability(HEntity entity)
{
    vector<STerrainPassability>::iterator it=m_Passabilities.begin();
    for (;it != m_Passabilities.end();++it)
    {
        if (entity->m_classes.IsMember(it->m_Type))
            return *it;
    }
    return m_DefaultPassability;
}

bool CTerrainProperties::IsPassable(HEntity entity)
{
    return GetPassability(entity).m_Passable;
}

double CTerrainProperties::GetSpeedFactor(HEntity entity)
{
    return GetPassability(entity).m_SpeedFactor;
}