//
// C++ Interface: net_hattrickconnector
//
// Description:
//
//
// Author: Matthias Feistel <hitman_47@users.sourceforge.net>, (C) 2007
//
// Copyright: See COPYING file that comes with this distribution
//
//


#include <vdr/skins.h>
#include <vdr/remote.h>
#include <vdr/menu.h>
#include "debug.h"
#include "config.h" // wegen PLUGIN_NAME/PLUGIN_VERSION
#include "settings.h" // benötigt, wegen cookies
#include "utils.h"
#include "ext/xmlParser/xmlParser.h"
#include "net_hattrickconnector.h"
#include "net_httpsocket.h"


int cHattrickConnector::Login(std::string username, std::string securitycode)
{
    XMLNode xMainNode;
    XMLNode xNode;
    std::string url, document;
    std::stringstream url_stringstream;

    /*
     *  recommended server lookup:
     */

    if( GetHttp( document, "http://www.hattrick.org/Common/menu.asp?outputType=XML") != 0 )
        return 1;

    // erhaltene XML-Daten durchforsten:
    XMLResults xe;
    xMainNode=XMLNode::parseString(document.c_str(),"HattrickData",&xe);
    rlog << "ERROR: " << XMLNode::getError( xe.error );

    if(xe.error)
        return 1;


    /*
     *  login onto recommended server:
     */

    xMainNode=XMLNode::parseString(document.c_str(),"HattrickData");
    xNode=xMainNode.getChildNode("RecommendedURL");

    std::string recommendedurl = xNode.getText();

    recommendedurl.append("/");  // so wird ein Schuh und eine vollstaendige URL daraus.

    cSettings::GetInstance().SetRecommendedurl( recommendedurl.c_str() );
    rlog << "cSettings::GetInstance().GetRecommendedurl()" << cSettings::GetInstance().GetRecommendedurl();

    url_stringstream << cSettings::GetInstance().GetRecommendedurl() <<
    	"Common/default.asp?outputType=XML&actionType=login&loginType=CHPP&Loginname=" <<
    	username << "&readonlypassword=" << securitycode;
    url_stringstream>> url;

    if( GetHttp(document, url) )
        return 1;

    xMainNode=XMLNode::parseString(document.c_str(),"HattrickData");
    xNode=xMainNode.getChildNode("ActionSuccessful");

    if(xe.error)
        return 1;
    
    if( strcmp( xNode.getText(), "True" ) != 0 )
        return 1;

    return 0;
}


int cHattrickConnector::Logout()
{
    std::string url, document;
    std::stringstream url_stringstream;

////    url_stringstream << cSettings::GetInstance().GetRecommendedurl() << "Common/default.asp?outputType=XML&actionType=logout";
    url_stringstream << cSettings::GetInstance().GetRecommendedurl() << "Common/default.asp?actionType=logoutfinal";
    url_stringstream>> url;



    if( GetHttp(document, url ) )
        return 1;

    cSettings::GetInstance().SetCookie ( "" );  //stoert die Timerfunktion

    return 0;
}


int cHattrickConnector::GetData()
{
    std::string url, document;
    std::stringstream url_stringstream;

    url_stringstream << cSettings::GetInstance().GetRecommendedurl() << "Common/teamDetails.asp?outputType=XML&actionType=view";
    url_stringstream>> url;

    if(GetHttp(document, url) )
        return 1;

    return 0;
}


/**
 * Find the TeamID for the provided Teamname
 * @param: long teamid
 * 
 * @returns: errorlevel
 */
int cHattrickConnector::SearchTeam(long &teamid, std::string teamname)
{
    std::string url, document;
    std::stringstream url_stringstream;

    XMLNode xMainNode;
    XMLNode xNode;

    // Text in Urltext konvertieren:
    teamname = Utility::rfc1738_encode(teamname);

    url_stringstream << cSettings::GetInstance().GetRecommendedurl() <<
    "Common/search.asp?outputType=XML&actionType=view&searchType=BEGINS&searchWhat=team&teamName=" <<
    teamname;
    url_stringstream >> url;

    if( GetHttp(document, url) )  // if not zero, a failure occured
        return 1;

    rlog << "SUCHRESULTAT: " << document;

    xMainNode=XMLNode::parseString(document.c_str(),"HattrickData");
    xNode=xMainNode.getChildNode("SearchResults").getChildNode("Result").getChildNode("ResultID");

    if( xNode.getText() )
        teamid = atol( xNode.getText() );
    else
        teamid = atol(teamname.c_str() ); // Hier nehmen wir an, dass im setup als Teamfavorit die TeamID
    // eingegeben wurde, um so das Umlautproblem zu umgehen.
    return 0;
}


/*
 * @param: teamid = 0 (eigenes Team wird gewaehlt)
 * 
 */
int cHattrickConnector::GetMatches(std::string &document, long teamid)
{
    std::string url, s;
    std::stringstream url_stringstream;

    s = itoa(teamid) ;
    if(teamid == 0)
        s.clear();

    url_stringstream << cSettings::GetInstance().GetRecommendedurl() <<
    "Common/matches.asp?outputType=XML&actionType=view&teamID=" << s;
    url_stringstream>> url;

    if( GetHttp(document, url) )
        return 1;

    return 0;
}


/**
 * Function tries to find the next match (either OnGoing or next in list)
 * 
 * return: MatchID
 */
int cHattrickConnector::GetNextMatch(long TeamID, long &MatchID,
                                     time_t &Time_NextMatch,
                                     std::string &HomeTeamName, long &HomeTeamID,
                                     std::string &AwayTeamName, long &AwayTeamID)
{
    std::string document, timestring;
    XMLNode xMainNode, xNode;

    GetMatches(document, TeamID);

    xMainNode=XMLNode::parseString(document.c_str(),"HattrickData");
    xNode=xMainNode.getChildNode("Team").getChildNode("MatchList");
    int n=xNode.nChildNode("Match");

    for (int i=0; i<n; i++)
    {
        // Beim ersten Auffinden von ONGOING wird die Suche beendet
        if( !strcmp( xNode.getChildNode("Match",i).getChildNode("Status").getText(), "ONGOING" ) ||
                !strcmp( xNode.getChildNode("Match",i).getChildNode("Status").getText(), "UPCOMING" ) )
        {
            MatchID = atol( xNode.getChildNode("Match",i).getChildNode("MatchID").getText() );

            timestring = xNode.getChildNode("Match",i).getChildNode("MatchDate").getText();

            HattrickDateToTimestamp( Time_NextMatch, timestring );

            HomeTeamName = xNode.getChildNode("Match",i).getChildNode("HomeTeam").getChildNode("HomeTeamName").getText();
            HomeTeamID = atol( xNode.getChildNode("Match",i).getChildNode("HomeTeam").getChildNode("HomeTeamID").getText() );
            AwayTeamName = xNode.getChildNode("Match",i).getChildNode("AwayTeam").getChildNode("AwayTeamName").getText();
            AwayTeamID = atol( xNode.getChildNode("Match",i).getChildNode("AwayTeam").getChildNode("AwayTeamID").getText() );

            return 0;
        }
    }

    return 0;
}


/**
 * Registers matches on hattrick.org server, so data will only be sent
 * to us if there had been a new event
 * 
 * return: 0=no error; 1=http error
 */
int cHattrickConnector::Live_addMatch( long MatchID )
{
    std::string document, url;
    ////    XMLNode xMainNode, xNode;
    std::stringstream url_stringstream;

    url_stringstream << cSettings::GetInstance().GetRecommendedurl() <<
    "Common/live.asp?outputType=XML&actionType=addMatch" <<
    "&matchID=" << MatchID;

    url_stringstream >> url;

    if( GetHttp(document, url) )
        return 1;

    // Hier erhalten wir ausserdem bereits die XML-Daten der Matches.
    // Deshalb fuettern wir diese auch in die TeamContainer
    XML_to_TeamContainer( document );

    return 0;
}


int cHattrickConnector::XML_to_TeamContainer( std::string document )
{

    std::map<long, cTeamData> TeamdataMap;
    std::map<const long, cTeamData>::iterator el;
    cTeamData teamdata;

    std::string s;

    XMLNode xMainNode, xNode,
    xNode_EventList;

    // erhaltene XML-Daten durchforsten:
    XMLResults xe;
    xMainNode=XMLNode::parseString(document.c_str(),"HattrickData",&xe);
    rlog << "ERROR: " << XMLNode::getError( xe.error );

    if(xe.error)
        return 1;

    /////////////////////////////////////////////////////////////////////
    // FUNKTION: gespielte Zeit ausrechnen:
    time_t time_FetchedDate = 0;
    s = xMainNode.getChildNode("FetchedDate").getText();

    xNode=xMainNode.getChildNode("MatchList");
    int n=xNode.nChildNode("Match");

    // Matches-Schleife
    for (int i=0; i<n; i++)
    {
        TeamdataMap = cSettings::GetInstance( ).GetTeamContainer( );

        teamdata.MatchID = atol( xNode.getChildNode("Match",i).getChildNode("MatchID").getText() );
        teamdata.NextMatchID = teamdata.MatchID;

        // bereits gesammelte Daten hier wieder laden:
        el = TeamdataMap.find( teamdata.MatchID );

        if( el->second.MatchID ) // Nur wenn ein TeamContainer fuer die hier gefundene MatchID existiert, laden
            teamdata = el->second;

        teamdata.HomeTeamGoals = atol( xNode.getChildNode("Match",i).getChildNode("HomeGoals").getText() );
        teamdata.AwayTeamGoals = atol( xNode.getChildNode("Match",i).getChildNode("AwayGoals").getText() );

        teamdata.HomeTeamName =  xNode.getChildNode("Match",i).getChildNode("HomeTeam").getChildNode("HomeTeamName").getText();
        teamdata.AwayTeamName =  xNode.getChildNode("Match",i).getChildNode("AwayTeam").getChildNode("AwayTeamName").getText();

        xNode_EventList=xNode.getChildNode("Match",i).getChildNode("EventList");
        int n2=xNode_EventList.nChildNode("Event");

        for (int j = 0; j < n2; j++)
        {
            cEventList EventList;
            EventList.Minute = atoi(xNode_EventList.getChildNode("Event",j).getChildNode("Minute").getText());
            EventList.EventText = xNode_EventList.getChildNode("Event",j).getChildNode("EventText").getText();
            int EventIndex = atoi( xNode_EventList.getChildNode("Event",j).getAttribute("Index"));


            if( ( teamdata.EventContainer.size() < EventIndex ) && ( (EventIndex - 1) > j ) ) // neues Event gefunden
            {
                rlog << "NEWEVENT: ";
                //Infobox aehnlich der VDR_info oeffnen:
                // Mit SkinEnigma lassen sich die OsdMessages scrollen:
                // Damit ist dann auch eine einzeilige Darstellung ausreichend.
                // Eine andere Loesung ist noch nicht in Sicht
                std::string s = EventList.EventText;
                std::stringstream sstream;

                sstream << teamdata.HomeTeamName.substr(0, SHORTTEAMNAME_LENGTH) << "-" << teamdata.AwayTeamName.substr(0, SHORTTEAMNAME_LENGTH) << ":" << EventList.EventText;
                s = sstream.str();

                cUtils::html2text( s, s );  // HTML-Code entfernen

                int Keys = Skins.QueueMessage(mtInfo, s.c_str(), REFRESHINTERVAL / 1000 - 15, REFRESHINTERVAL / 1000 - 15 + 2);

                if(Keys == kOk)
                {
                    cRemote::CallPlugin("hattrick");
                }
            }

            teamdata.EventContainer[ EventIndex ] =  EventList;
        } // for-Schleife EventList

        // Gewonnene Daten im den TeamContainer ablegen:
        TeamdataMap[ teamdata.NextMatchID ] = teamdata;
        cSettings::GetInstance().SetTeamContainer(TeamdataMap);

    }  // for-Schleife Matches

    /////////////////////////////////////////////////////////////////////
    // FUNKTION: Gespielte Zeit ausrechnen:

    TeamdataMap = cSettings::GetInstance( ).GetTeamContainer( );

    for (el = TeamdataMap.begin(); el != TeamdataMap.end(); ++el)
    {
        teamdata = el->second;

        char buff[4096];
        snprintf(buff, sizeof(buff), "%s-%s\t%d-%d\t(%d.)",
                 teamdata.HomeTeamName.c_str(), teamdata.AwayTeamName.c_str(),
                 teamdata.HomeTeamGoals, teamdata.AwayTeamGoals,
                 teamdata.TimePlayed);


        HattrickDateToTimestamp( time_FetchedDate, s ) ;
        int tmp_time = difftime( time_FetchedDate, teamdata.time_nextMatch ) / 60;

        if ( tmp_time < -2 )  // Spiel liegt weiter als 3 Minuten in der Zukunft
            teamdata.TimePlayed = 0;
        else if ( tmp_time >= 45 && tmp_time <= 60 )  // Halbzeitpause abziehen
            teamdata.TimePlayed = 45;
        else if ( tmp_time >= 60 )  // Halbzeitpause abziehen
            teamdata.TimePlayed = tmp_time - 15;
        else
            teamdata.TimePlayed = tmp_time;

        // Gewonnene Daten im den TeamContainer ablegen:
        TeamdataMap[ teamdata.NextMatchID ] = teamdata;
        cSettings::GetInstance().SetTeamContainer(TeamdataMap);
    }
    /////////////////////////////////////////////////////////////////////

    return 0;
}


int cHattrickConnector::Live( )
{
    std::string document, url;
    XMLNode xMainNode, xNode,
    xNode_EventList;
    std::stringstream url_stringstream;

    url_stringstream << cSettings::GetInstance().GetRecommendedurl() <<
    "Common/live.asp?outputType=XML&actionType=viewNew";
    url_stringstream >> url;

    if( GetHttp(document, url) )
        return 1;

    XML_to_TeamContainer(document);

    return 0;
}


/**
 * Converts the Hattrick-MatchDate-String to timestamp format
 * 
 * TODO: Muss noch weiter getestet werden (zweistellige Monate usw.)
 * 
 */
int cHattrickConnector::HattrickDateToTimestamp(time_t &timestamp, std::string timestring)
{
    int Y, M, D, h, m, s;
    char empty_void;
    std::istringstream ii(timestring);
    struct tm timeinfo;

    Y = 0;
    M = 0;
    D = 0;
    h = 0;
    m = 0;
    s = 0;

    ii >> Y >> empty_void >> M >> empty_void >> D >>
    h >> empty_void >> m >> empty_void >> s;

    timeinfo.tm_year = Y - 1900;
    timeinfo.tm_mon = M-1;
    timeinfo.tm_mday = D;
    timeinfo.tm_hour = h;
    timeinfo.tm_min = m;
    timeinfo.tm_sec = s;
    timestamp = mktime( &timeinfo );

    return 0;
}


// More complex functions:

/**
 * This function feeds the TeamData with date of next match and information
 * about the involved teams will be downloaded.
 *
 * return: 0=no error
 */
int cHattrickConnector::LoadHattrickData()
{
    std::list<std::string> FavouriteList = cSettings::GetInstance( ).GetFavouriteList( );
    std::map<long, cTeamData> TeamdataMap = cSettings::GetInstance( ).GetTeamContainer( );
    std::map<const long, cTeamData>::iterator el;
    long TeamID;
    std::string it_string;

    cHattrickConnector *hc = new cHattrickConnector();

    if ( hc->Login( cSettings::GetInstance().GetUsername(), cSettings::GetInstance().GetSecuritycode() ) != 0 )
    {
        rlog << "login failed";
        return 1;  // kein Fehler, der den Start von VDR behindern sollte;
        // die Daten koennten im weiteren Stuendlich abgerufen werden,
        // um einen Timer definieren zu koennen.
    }

    // Hier werden die gespeicherten Favoriten aus dem Setup in NextMatchID aufgeloest und
    // die erhaltenen Daten in den TeamContainer eingetragen.
    for(std::list<std::string>::iterator it = FavouriteList.begin(); it != FavouriteList.end(); it++)
    {
        it_string = *it;

        hc->SearchTeam( TeamID, it_string );

        cTeamData teamdata;

        hc->GetNextMatch( TeamID, teamdata.NextMatchID,
                          teamdata.time_nextMatch,
                          teamdata.HomeTeamName, teamdata.HomeTeamID,
                          teamdata.AwayTeamName, teamdata.AwayTeamID );

        /////////////////////////
        // team short names:
        // implemenation still to discuss, because extra http-requests would have to be done in
        // order to get the information out of teamDetails.asp
        // Alternative: shorten the teamnames to five letters or so.
        /////////////////////////

        // Eintrag mit Index MatchID in den TeamContainer mit bereits gewonnenen Daten:
        TeamdataMap[ teamdata.NextMatchID ] = teamdata;
        cSettings::GetInstance().SetTeamContainer(TeamdataMap);

        // Wenn Timer gewuenscht, wird das Spiel kurz vor Spielbeginn via
        // Timer abgerufen.
        hc->Live_addMatch( teamdata.NextMatchID );
    }

    // eigenes Team automatisch einbinden?:
    if(cSettings::GetInstance().IsFavour_ownteam())
    {
        cTeamData teamdata;
        hc->GetNextMatch( 0, teamdata.NextMatchID,
                          teamdata.time_nextMatch,
                          teamdata.HomeTeamName, teamdata.HomeTeamID,
                          teamdata.AwayTeamName, teamdata.AwayTeamID );

        // Eintrag mit Index MatchID in den TeamContainer mit bereits gewonnenen Daten:
        TeamdataMap[ teamdata.NextMatchID ] = teamdata;
        cSettings::GetInstance().SetTeamContainer(TeamdataMap);

        hc->Live_addMatch( teamdata.NextMatchID );
    }

    delete(hc);
    hc = 0;

    return 0;
}


///////////////////////////////////////////////////////////////7
////////// HTTP_functions:

int cHattrickConnector::GetHttp(std::string &document, std::string url)
{
    rlog << "GetHttp";

    SocketHandler h;
    bool complete = false;

    My_HttpGetSocket s(h, url);

    unsigned char *p = new unsigned char[HTTPGET_MAXSIZE]; // max doc size
    memset(p, 0, HTTPGET_MAXSIZE);
    s.SetDataPtr(p, HTTPGET_MAXSIZE - 1); // preserve zero at end of document

    h.Add(&s);
    h.Select(0,1);

    while (h.GetCount())
    {
        h.Select(0,1);
    }

    complete = s.Complete();
    std::string str = (char*)s.GetDataPtr();

    // check for zero data, so our request would have failed (return 1)
    // check for vb ASP runtime error on hattrick server
    // check for downtimes: downweb.hattrick.org
    rlog << "DOK: " << (char *)s.GetDataPtr();
    if (complete && str.compare("") != 0
            && str.find("Microsoft VBScript runtime") == std::string::npos
            ////            && str.find("downweb.hattrick.org") == std::string::npos // actually, these checks are not needed, because we check login procedure for a tag and cancel there any further work, if failure occurred.
       )
    {
        document = static_cast<std::string>( (char *)s.GetDataPtr());
    }
    else
        return 1;

    rlog << "GetHttp - delete DataPtr";
    delete[] s.GetDataPtr();

    return 0;
}


