#include "xmlparser.h"
#include "../config.h"
#include "../extensions/helpers.h"

using namespace std;

cXmlParser::cXmlParser(void) {
    sdOsd = NULL;
    view = NULL;
    globals = NULL;
    skinSetup = NULL;
}

cXmlParser::~cXmlParser() {
}

/*********************************************************************
* PUBLIC Functions
*********************************************************************/
bool cXmlParser::ReadView(cView *view, string xmlFile) {
    if (!view)
        return false;
    this->view = view;
    string xmlPath = GetPath(xmlFile);
    if (! ReadXMLFile(xmlPath.c_str()) )
        return false;
    if (! Validate() )
        return false;
    if (! SetDocument() )
        return false;
    if (! CheckNodeName( view->GetViewName() ))
        return false;
    return true;
}

bool cXmlParser::ParseView(void) {
    if (!view)
        return false;
    vector<stringpair> rootAttribs = ParseAttributes();
    view->SetAttributes(rootAttribs);

    cViewMenu *menuView = dynamic_cast<cViewMenu*>(view);
    cViewTracks *tracksView = dynamic_cast<cViewTracks*>(view);

    if (!LevelDown())
        return false;

    do {
        if (view->ValidViewElement(NodeName())) {
            ParseViewElement(NodeName());
        } else if (menuView) {
            if (menuView->ValidSubView(NodeName())) {
                ParseSubView(NodeName());
            } else if (view->ValidViewList(NodeName())) {
                ParseViewList();
            } else {
                esyslog("skindesigner: unknown node %s", NodeName());
                return false;
            }
        } else if (tracksView) {
            if (view->ValidViewList(NodeName())) {
                ParseViewList();
            } else {
                esyslog("skindesigner: unknown node %s", NodeName());
                return false;
            }
        } else {
            esyslog("skindesigner: unknown view %s", NodeName());
            return false;
        }
    } while (NextNode());

    return true;
}

bool cXmlParser::ParsePluginView(void) {
    if (!view)
        return false;
    vector<stringpair> rootAttribs = ParseAttributes();
    view->SetAttributes(rootAttribs);

    if (!LevelDown())
        return false;

    do {
        if (CheckNodeName("viewelement")) {
            ParsePluginViewElement();
        } else if (CheckNodeName("grid")) {
            ParseGrid();
        } else if (CheckNodeName("tab")) {
            ParseViewTabPlugin();
        } else if (CheckNodeName("scrollbar")) {
            ParsePluginViewElement(true, false);
        } else if (CheckNodeName("tablabels")) {
            ParsePluginViewElement(false, true);
        } else {
            esyslog("skindesigner: unknown node %s in plugin view", NodeName());
        }
    } while (NextNode());

    return true;
}

bool cXmlParser::ReadPluginView(string templateName) {
    string xmlPath = GetPath(templateName);
    DeleteDocument();
    if (! ReadXMLFile(xmlPath.c_str()) )
        return false;
    if (! Validate() )
        return false;
    if (! SetDocument() )
        return false;
    return true;
}

bool cXmlParser::ParsePluginView(string plugName, int plugId, int menuNumber, int menuType) {
    if (!view)
        return false;
    cViewMenu *menuView = dynamic_cast<cViewMenu*>(view);
    if (!menuView) {
        return false;
    }
    vector<pair<string, string> > attribs = ParseAttributes();

    if (!LevelDown())
        return false;

    cSubView *plugView = cViewMenu::CreatePluginview(plugName.c_str(), plugId, menuNumber, menuType);
    plugView->SetAttributes(attribs);
    menuView->AddPluginview(plugView);
    do {
        if (plugView->ValidViewElement(NodeName())) {
            ParseViewElement(NodeName(), plugView);
        } else if (plugView->ValidViewList(NodeName())) {
            ParseViewList(plugView);
        } else if (CheckNodeName("tab")) {
            ParseViewTab(plugView);
        } else {
            esyslog("skindesigner: error - unknown %s", NodeName());
            return false;
        }
    } while (NextNode());
    LevelUp();
    return true;
}

bool cXmlParser::ReadGlobals(cGlobals *globals, string xmlFile) {
    this->globals = globals;
    string xmlPath = GetPath(xmlFile);
    DeleteDocument();
    if (! ReadXMLFile(xmlPath.c_str()) )
        return false;
    if (! Validate() )
        return false;
    if (! SetDocument() )
        return false;
    if (! CheckNodeName("globals") )
        return false;
    return true;
}

bool cXmlParser::ParseGlobals(void) {
    if (!LevelDown())
        return false;
    do {
        if (CheckNodeName("colors")) {
            ParseGlobalColors();
        } else if (CheckNodeName("variables")) {
            ParseGlobalVariables();
        } else if (CheckNodeName("fonts")) {
            ParseGlobalFonts();
        } else if (CheckNodeName("translations")) {
            ParseTranslations();
        }
    } while (NextNode());
    return true;
}

bool cXmlParser::ReadSkinSetup(cSkinSetup *skinSetup, string xmlFile) {
    this->skinSetup = skinSetup;
    if (! ReadXMLFile(xmlFile.c_str()) )
        return false;
    if (! Validate() )
        return false;
    if (! SetDocument() )
        return false;
    if (! CheckNodeName("setup") )
        return false;
    return true;
}

bool cXmlParser::ParseSkinSetup(string skin) {
    if (!LevelDown())
        return false;
    do {
        if (CheckNodeName("menu")) {
            ParseSetupMenu();
        } else if (CheckNodeName("translations")) {
            ParseTranslations();
        }
    } while (NextNode());
    return true;
}
/*********************************************************************
* PRIVATE Functions
*********************************************************************/
bool cXmlParser::ParseSubView(string name) {
    if (!view)
        return false;
    cViewMenu *menuView = dynamic_cast<cViewMenu*>(view);
    if (!menuView) {
        return false;
    }
    vector<pair<string, string> > attribs = ParseAttributes();

    if (!LevelDown())
        return false;

    cSubView *subView = cViewMenu::CreateSubview(name.c_str());
    subView->SetAttributes(attribs);
    menuView->AddSubview(name.c_str(), subView);

    do {
        if (subView->ValidViewElement(NodeName())) {
            ParseViewElement(NodeName(), subView);
        } else if (subView->ValidViewList(NodeName())) {
            ParseViewList(subView);
        } else if (CheckNodeName("tab")) {
            ParseViewTab(subView);           
        } else {
            return false;
        }
    } while (NextNode());
    LevelUp();
    return true;
}

void cXmlParser::ParseViewElement(string name, cView *subView) {
    if (!view)
        return;
    const char *viewElementName = NodeName();
    vector<stringpair> attributes = ParseAttributes();

    if (!LevelDown())
        return;

    //correct detailheader name
    if (subView) {
        if (!strcmp(subView->GetViewName(), "menudetailedepg") && !name.compare("detailheader")) {
            name = "detailheaderepg";
        } else if (!strcmp(subView->GetViewName(), "menudetailedrecording") && !name.compare("detailheader")) {
            name = "detailheaderrec";
        } else if (startswith(subView->GetViewName(), "menuplugin") && !name.compare("detailheader")) {
            name = "detailheaderplugin";
        }
    }

    cViewElement *viewElement = cViewElement::CreateViewElement(name.c_str(), view->GetViewName());
    if (!viewElement) {
        LevelUp();
        return;
    }

    viewElement->SetOsd(sdOsd);
    viewElement->SetAttributes(attributes);
    do {
        if (!CheckNodeName("areacontainer") && !CheckNodeName("area") && !CheckNodeName("areascroll")) {
            esyslog("skindesigner: invalid tag \"%s\" in viewelement", NodeName());
            continue;
        }
        cAreaNode *area = NULL;
        if (CheckNodeName("area") || CheckNodeName("areascroll")) {
            area = ParseArea();
        } else {
            area = ParseAreaContainer();
        }
        viewElement->AddArea(area);
    } while (NextNode());

    LevelUp();

    if (subView)
        subView->AddViewElement(viewElementName, viewElement);
    else
        view->AddViewElement(viewElementName, viewElement);

}

void cXmlParser::ParsePluginViewElement(bool isScrollbar, bool isTabLabels) {
    if (!view)
        return;
    cViewPlugin *pluginView = dynamic_cast<cViewPlugin*>(view);
    if (!pluginView)
        return;

    vector<stringpair> attributes = ParseAttributes();

    if (!LevelDown())
        return;

    cViewElement *viewElement = NULL;
    if (isScrollbar) {
        viewElement = new cVeDmScrollbar();
    } else if (isTabLabels) {
        viewElement = new cVeDmTablabels();
    } else {
        viewElement = new cVePlugin();        
    }

    viewElement->SetOsd(sdOsd);
    viewElement->SetAttributes(attributes);
    do {
        if (!CheckNodeName("areacontainer") && !CheckNodeName("area") && !CheckNodeName("areascroll")) {
            esyslog("skindesigner: invalid tag \"%s\" in viewelement", NodeName());
            continue;
        }
        cAreaNode *area = NULL;
        if (CheckNodeName("area") || CheckNodeName("areascroll")) {
            area = ParseArea();
        } else {
            area = ParseAreaContainer();
        }
        viewElement->AddArea(area);
    } while (NextNode());

    LevelUp();
    
    if (isScrollbar) {
        pluginView->AddScrollbar(dynamic_cast<cVeDmScrollbar*>(viewElement));
    } else if (isTabLabels) {
        pluginView->AddTablabels(dynamic_cast<cVeDmTablabels*>(viewElement));
    } else {
        pluginView->AddViewElement(dynamic_cast<cVePlugin*>(viewElement));
    }
}

void cXmlParser::ParseViewList(cView *subView) {
    if (!view)
        return;

    const char *name = NULL;
    if (subView)
        name = subView->GetViewName();
    else
        name = view->GetViewName();

    vector<stringpair> attribs = ParseAttributes();
    cViewList *viewList = cViewList::CreateViewList(name);
    viewList->SetAttributes(attribs);

    if (!LevelDown())
        return;

    do {
        if (CheckNodeName("currentelement")) {
            cViewElement *currentElement = cViewList::CreateCurrentElement(name);
            currentElement->SetOsd(sdOsd);
            vector<stringpair> attribsList = ParseAttributes();
            currentElement->SetAttributes(attribsList);
            if (!LevelDown())
                continue;
            do {
                if (!CheckNodeName("areacontainer") && !CheckNodeName("area") && !CheckNodeName("areascroll")) {
                    esyslog("skindesigner: invalid tag \"%s\" in viewelement", NodeName());
                    continue;
                }
                cAreaNode *area = NULL;
                if (CheckNodeName("area") || CheckNodeName("areascroll")) {
                    area = ParseArea();
                } else {
                    area = ParseAreaContainer();
                }
                currentElement->AddArea(area);
            } while (NextNode());
            LevelUp();
            viewList->AddCurrentElement(currentElement);
        } else if (CheckNodeName("listelement")) {
            cViewElement *listElement = cViewList::CreateListElement(name);
            listElement->SetOsd(sdOsd);
            vector<stringpair> attribsList = ParseAttributes();
            listElement->SetAttributes(attribsList);            
            if (!LevelDown())
                return;
            do {
                if (!CheckNodeName("areacontainer") && !CheckNodeName("area") && !CheckNodeName("areascroll")) {
                    esyslog("skindesigner: invalid tag \"%s\" in viewelement", NodeName());
                    continue;
                }
                cAreaNode *area = NULL;
                if (CheckNodeName("area") || CheckNodeName("areascroll")) {
                    area = ParseArea();
                } else {
                    area = ParseAreaContainer();
                }
                listElement->AddArea(area);
            } while (NextNode());
            LevelUp();
            viewList->AddListElement(listElement);
        }
    } while (NextNode());
    LevelUp();
    
    if (subView)
        subView->AddViewList(viewList);
    else
        view->AddViewList(viewList);
    
}

void cXmlParser::ParseViewTab(cView *subView) {
    if (!view || !subView)
        return;
    cSubView *menuSubView = dynamic_cast<cSubView*>(subView);
    if (!menuSubView) {
        return;
    }
    vector<stringpair> attribs = ParseAttributes();
    cArea *tab = new cArea();
    tab->SetOsd(sdOsd);
    tab->SetTab();
    tab->SetAttributes(attribs);
    ParseFunctionCalls(tab);
    menuSubView->AddTab(tab);
}

void cXmlParser::ParseViewTabPlugin(void) {
    if (!view)
        return;
    cViewPlugin *plugView = dynamic_cast<cViewPlugin*>(view);
    if (!plugView) {
        return;
    }
    vector<stringpair> attribs = ParseAttributes();
    cArea *tab = new cArea();
    tab->SetOsd(sdOsd);
    tab->SetTab();
    tab->SetAttributes(attribs);
    ParseFunctionCalls(tab);
    plugView->AddTab(tab);
}

void cXmlParser::ParseGrid(void) {
    if (!view)
        return;
    cViewPlugin *pluginView = dynamic_cast<cViewPlugin*>(view);
    if (!pluginView)
        return;

    vector<stringpair> attributes = ParseAttributes();

    if (!LevelDown())
        return;

    cViewGrid *viewGrid = new cViewGrid();
    viewGrid->SetAttributes(attributes);
    cGridElement *gridElement = new cGridElement();
    gridElement->SetOsd(sdOsd);

    do {
        if (!CheckNodeName("areacontainer") && !CheckNodeName("area") && !CheckNodeName("areascroll")) {
            esyslog("skindesigner: invalid tag \"%s\" in grid", NodeName());
            continue;
        }
        cAreaNode *area = NULL;
        if (CheckNodeName("area") || CheckNodeName("areascroll")) {
            area = ParseArea();
        } else {
            area = ParseAreaContainer();
        }
        gridElement->AddArea(area);
    } while (NextNode());
    LevelUp();
    
    viewGrid->AddGridElement(gridElement);
    pluginView->AddViewGrid(viewGrid);
}

cArea *cXmlParser::ParseArea(void) {
    vector<stringpair> attribs = ParseAttributes();
    cArea *area = new cArea();
    area->SetOsd(sdOsd);
    if (CheckNodeName("areascroll")) {
        area->SetScrolling();
    }
    area->SetAttributes(attribs);
    ParseFunctionCalls(area);
    return area;    
}

cAreaContainer *cXmlParser::ParseAreaContainer(void) {
    vector<stringpair> attribs = ParseAttributes();

    cAreaContainer *areaContainer = new cAreaContainer();
    areaContainer->SetAttributes(attribs);

    if (!LevelDown())
        return areaContainer;
    do {
        if (!CheckNodeName("area") && !CheckNodeName("areascroll")) {
            esyslog("skindesigner: invalid tag \"%s\" in areacontainer", NodeName());
            continue;
        }
        cArea *area = ParseArea();
        areaContainer->AddArea(area);
    } while (NextNode());
    LevelUp();
    return areaContainer;
}

void cXmlParser::ParseFunctionCalls(cArea *area) {
    if (!view)
        return;
    if (!LevelDown())
        return;
    do {
        if (CheckNodeName("loop")) {
            vector<stringpair> attribs = ParseAttributes();
            cFunction *f = area->AddFunction(NodeName(), attribs);
            cFuncLoop *loopFunc = dynamic_cast<cFuncLoop*>(f);
            if (!loopFunc)
                continue;
            ParseLoopFunctionCalls(area, loopFunc);
        } else if (area->ValidFunction(NodeName())) {
            vector<stringpair> attribs = ParseAttributes();
            area->AddFunction(NodeName(), attribs);
        }
    } while (NextNode());
    LevelUp();
}

void cXmlParser::ParseLoopFunctionCalls(cArea *area, cFuncLoop *loopFunc) {
    if (!view)
        return;
    if (!LevelDown())
        return;
    do {
        if (area->ValidFunction(NodeName())) {
            vector<stringpair> attribs = ParseAttributes();
            area->AddFunction(NodeName(), attribs, loopFunc);
        }
    } while (NextNode());
    LevelUp();
}

void cXmlParser::ParseGlobalColors(void) {
    if (!LevelDown())
        return;
    do {
        if (!CheckNodeName("color")) {
            continue;
        }
        string attributeName = "name";
        string colorName  = "";
        string colorValue = "";
        bool ok = GetAttribute(attributeName, colorName);
        if (ok) {
            ok = GetNodeValue(colorValue);
            if (ok)
                InsertColor(colorName, colorValue);
        }
    } while (NextNode());
    LevelUp();
}

void cXmlParser::InsertColor(string name, string value) {
    if (value.size() != 8)
        return;
    std::stringstream str;
    str << value;
    tColor colVal;
    str >> std::hex >> colVal;
    globals->AddColor(name, colVal);
}

void cXmlParser::ParseGlobalVariables(void) {
    if (!LevelDown())
        return;
    do {
        if (!CheckNodeName("var")) {
            continue;
        }
        string attributeName = "name";
        string attributeType = "type";
        string varName  = "";
        string varType  = "";
        string varValue = "";

        bool ok1 = GetAttribute(attributeName, varName);
        bool ok2 = GetAttribute(attributeType, varType);

        if (ok1 && ok2) {
            bool ok = GetNodeValue(varValue);
            if (ok)
                InsertVariable(varName, varType, varValue);
        }
    } while (NextNode());
    LevelUp();
}

void cXmlParser::InsertVariable(string name, string type, string value) {
    if (!type.compare("int")) {
        int val = atoi(value.c_str());
        globals->AddInt(name, val);
    } else if (!type.compare("double")) {
        globals->AddDouble(name, value);
    } else if (!type.compare("string")) {
        globals->AddString(name, value);
    }
}

void cXmlParser::ParseGlobalFonts(void) {
    if (!LevelDown())
        return;
    do {
        if (!CheckNodeName("font")) {
            continue;
        }
        string attributeName = "name";
        string fontName = "";
        string fontValue = "";

        bool ok = GetAttribute(attributeName, fontName);
        if (ok) {
            ok = GetNodeValue(fontValue);
            if (ok) {
                globals->AddFont(fontName, fontValue);
            }
        }
    } while (NextNode());
    LevelUp();
}

void cXmlParser::ParseTranslations(void) {
    if (!LevelDown())
        return;
    do {
        if (!CheckNodeName("token")) {
            continue;
        }
        string attributeName = "name";
        string tokenName = "";

        if (!GetAttribute(attributeName, tokenName))
            continue;

        if (!LevelDown())
            continue;

        stringmap tokenTranslations;
        do {
            if (!CheckNodeName("trans")) {
                continue;
            }
            string attributeName = "lang";
            string language = "";
            if (!GetAttribute(attributeName, language))
                continue;
            string translation = "";
            if (!GetNodeValue(translation))
                continue;
            tokenTranslations.insert(stringpair(language, translation));
        } while (NextNode());
        LevelUp();

        if (globals) {
            globals->AddTranslation(tokenName, tokenTranslations);
        } else if (skinSetup) {
            skinSetup->SetTranslation(tokenName, tokenTranslations);
        }

    } while (NextNode());
    LevelUp();
}

void cXmlParser::ParseSetupMenu(void) {
    if (!skinSetup)
        return;
    if (!LevelDown())
        return;
    do {
        if (CheckNodeName("parameter")) {
            ParseSetupParameter();
        } else if (CheckNodeName("submenu")) {
            string attributeName = "name";
            string subMenuName = "";
            string attributeDisplayText = "displaytext";
            string subDisplayText = "";
            GetAttribute(attributeName, subMenuName);
            GetAttribute(attributeDisplayText, subDisplayText);
            skinSetup->SetSubMenu(subMenuName, subDisplayText);
            ParseSetupMenu();
        }
    } while (NextNode());
    skinSetup->SubMenuDone();
    LevelUp();
}

void cXmlParser::ParseSetupParameter(void) {
    if (!skinSetup)
        return;
    string attributeType = "type";
    string paramType = "";
    string attributeName = "name";
    string paramName = "";
    string attributeDisplayText = "displaytext";
    string paramDisplayText = "";
    string attributeHelpText = "helptext";
    string paramHelpText = "";
    string attributeMin = "min";
    string paramMin = "";
    string attributeMax = "max";
    string paramMax = "";
    string paramValue = "";
    string attributeOptions = "options";
    string paramOptions="";

    GetAttribute(attributeType, paramType);
    GetAttribute(attributeName, paramName);
    GetAttribute(attributeDisplayText, paramDisplayText);
    GetAttribute(attributeHelpText, paramHelpText);
    GetAttribute(attributeMin, paramMin);
    GetAttribute(attributeMax, paramMax);
    GetAttribute(attributeOptions, paramOptions);
    GetNodeValue(paramValue);

    skinSetup->SetParameter(paramType, paramName, paramDisplayText, paramHelpText, paramMin, paramMax, paramValue, paramOptions);
}

string cXmlParser::GetPath(string xmlFile) {
    string activeSkin = Setup.OSDSkin;
    string activeTheme = Setup.OSDTheme;
    string path = "";
    if (!xmlFile.compare("globals.xml")) {
        path = *cString::sprintf("%s%s/%s", *config.GetSkinPath(activeSkin), activeSkin.c_str(), xmlFile.c_str());
    } else if (!xmlFile.compare("theme.xml")) {
        path = *cString::sprintf("%s%s/themes/%s/%s", *config.GetSkinPath(activeSkin), activeSkin.c_str(), activeTheme.c_str(), xmlFile.c_str());
    } else if (!xmlFile.compare("setup.xml")) {
        path = *cString::sprintf("%s%s/%s", *config.GetSkinPath(activeSkin), activeSkin.c_str(), xmlFile.c_str());
    } else {
        path = *cString::sprintf("%s%s/xmlfiles/%s", *config.GetSkinPath(activeSkin), activeSkin.c_str(), xmlFile.c_str());
    }
    return path;
}

