// This file is a part of the xMule Project.
//
// Copyright (c) 2004 Theodore R. Smith (hopeseekr@xmule.ws / http://www.xmule.ws/)
// DSA-1024 Fingerprint: 10A0 6372 9092 85A2 BB7F 907B CB8B 654B E33B F1ED
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

#ifdef PRECOMP
#	include "xmule-headers.h"
#else
#	include "ED2KLink.h"
#	include "otherfunctions.h"
#	include "resource.h"
#	include "wintypes.h"
#	include "xmule.h"
#endif

#include <wx/datetime.h>

#if 1
namespace
{
    struct autoFree
    {
        autoFree(TCHAR *p): m_p(p)
        {
        }
        ~autoFree()
        {
            free(m_p);
        }
        private:
        TCHAR *m_p;
        };
    inline unsigned int FromHexDigit(TCHAR digit)
    {
        switch (digit)
        {
        case('0'): return 0;
        case('1'): return 1;
        case('2'): return 2;
        case('3'): return 3;
        case('4'): return 4;
        case('5'): return 5;
        case('6'): return 6;
        case('7'): return 7;
        case('8'): return 8;
        case('9'): return 9;
        case('A'): return 10;
        case('B'): return 11;
        case('C'): return 12;
        case('D'): return 13;
        case('E'): return 14;
        case('F'): return 15;
        case('a'): return 10;
        case('b'): return 11;
        case('c'): return 12;
        case('d'): return 13;
        case('e'): return 14;
        case('f'): return 15;
        default: throw GetResString(IDS_ERR_ILLFORMEDHASH);
        }
    }
}

#endif

CED2KLink::~CED2KLink()
{
}

/////////////////////////////////////////////
// CED2KServerListLink implementation
/////////////////////////////////////////////
CED2KServerListLink::CED2KServerListLink(const TCHAR *address)
{
    m_address = address;
}

CED2KServerListLink::~CED2KServerListLink()
{
}

void
CED2KServerListLink::GetLink(wxString &lnk)
{
    lnk = ("ed2k://|serverlist|");
    lnk += m_address;
    lnk += ("|/");
}

CED2KServerListLink *
CED2KServerListLink::GetServerListLink()
{
    return this;
}

CED2KServerLink *
CED2KServerListLink::GetServerLink()
{
    return 0;
}

CED2KFileLink *
CED2KServerListLink::GetFileLink()
{
    return 0;
}

CED2KLink::LinkType
CED2KServerListLink::GetKind() const
{
    return kServerList;
}

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

/////////////////////////////////////////////
// CED2KServerLink implementation
/////////////////////////////////////////////
CED2KServerLink::CED2KServerLink(const TCHAR *ip, const TCHAR *port)
{
    m_ip = inet_addr(ip);
    //_tcstoul(port,0,10);:
    unsigned long ul = atoi(port);
    if (ul > 0xFFFF)
    throw GetResString(IDS_ERR_BADPORT);
    m_port = static_cast < uint16_t > (ul);
    m_defaultName = "Server ";
    m_defaultName += ip;
    m_defaultName += ":";
    m_defaultName += port;
}

CED2KServerLink::~CED2KServerLink()
{
}

void
CED2KServerLink::GetLink(wxString &lnk)
{
    in_addr adr;
    char buffer[32];
    lnk = ("ed2k://|server|");
    adr.s_addr = m_ip;
    lnk += inet_ntoa(adr);
    lnk += ("|");
    sprintf(buffer, "%d", static_cast < int > (m_port));
    lnk += buffer;
    lnk += ("|/");
}

CED2KServerListLink *
CED2KServerLink::GetServerListLink()
{
    return 0;
}

CED2KServerLink *
CED2KServerLink::GetServerLink()
{
    return this;
}

CED2KFileLink *
CED2KServerLink::GetFileLink()
{
    return 0;
}

CED2KLink::LinkType
CED2KServerLink::GetKind() const
{
    return kServer;
}

/////////////////////////////////////////////
// CED2KFileLink implementation
/////////////////////////////////////////////
CED2KFileLink::CED2KFileLink(const TCHAR *name, const TCHAR *size, const TCHAR *hash, const TCHAR *sources)
: m_name(name)
, m_size(size)
{
    SourcesList = NULL;
    if (strlen(hash) != 32)
    throw GetResString(IDS_ERR_ILLFORMEDHASH);
    for (int idx = 0 ; idx < 16 ;++idx)
    {
        m_hash[idx] = FromHexDigit( *hash++) *16;
        m_hash[idx] += FromHexDigit( *hash++);
    }
    if (sources)
    {
        TCHAR *pNewString = strdup(sources);
        autoFree liberator(pNewString);
        TCHAR *pCh = pNewString;
        TCHAR *pEnd;
        TCHAR *pIP;
        TCHAR *pPort;
        bool bAllowSources;
        TCHAR date[3];
        //COleDateTime expirationDate;
        wxDateTime expirationDate;
        int nYear, nMonth, nDay;
        uint16_t nCount = 0;
        uint32_t dwID;
        uint16_t nPort;
        uint32_t dwServerIP = 0;
        uint16_t nServerPort = 0;
        unsigned long ul;
        int nInvalid = 0;
        pCh = strstr(pCh, _T("sources"));
        if (pCh != NULL)
        {
            // point to char after "sources":
            pCh = pCh + 7;
            pEnd = pCh;
            // make pEnd point to the terminating NULL:
            while ( *pEnd) pEnd++;
            bAllowSources = true;
            // if there's an expiration date...
            if ( *pCh == _T('@') && (pEnd - pCh) > 7)
            {
                // after '@':
                pCh++;
                // terminate the two character string:
                date[2] = 0;
                date[0] = * (pCh++);
                date[1] = * (pCh++);
                nYear = atol(date) + 2000;
                date[0] = * (pCh++);
                date[1] = * (pCh++);
                nMonth = atol(date);
                date[0] = * (pCh++);
                date[1] = * (pCh++);
                nDay = atol(date);
                expirationDate.Set(nYear, (wxDateTime::Month) nMonth, nDay, 0, 0, 0, 0);
                bAllowSources = expirationDate.IsValid();
                if (bAllowSources) bAllowSources = (wxDateTime::UNow() < expirationDate);
            }
            // increment pCh to point to the first "ip:port" and check for sources
            if (bAllowSources &&++pCh < pEnd)
            {
                SourcesList = new CMemFile();
                // init to 0, we'll fix this at the end.:
                SourcesList->Write( &nCount, sizeof(nCount));
                // for each "ip:port" source string until the end
                // limit to prevent overflow (uint16_t due to CPartFile::AddClientSources)
#define MAXSHORT 65535
                while ( *pCh != 0 &&nCount < MAXSHORT)
                {
                    pIP = pCh;
                    // find the end of this ip:port string & start of next ip:port string.
                    if (pCh = strchr(pCh, _T(',')))
                    {
                        // terminate current "ip:port":
                        *pCh = 0;
                        // point to next "ip:port":
                        pCh++;
                    }
                    else
                    pCh = pEnd;
                    // if port is not present for this ip, go to the next ip.
                    if ((pPort = strchr(pIP, _T(':'))) == NULL)
                    {
                        nInvalid++;
                        continue;
                    }
                    // terminate ip string:
                    *pPort = 0;
                    // point pPort to port string.:
                    pPort++;
                    dwID = inet_addr(pIP);
                    //tcstoul( pPort, 0, 10 );:
                    ul = atoi(pPort);
                    nPort = static_cast < uint16_t > (ul);
                    // skip bad ips / ports
                    if (dwID == INADDR_NONE || dwID < 16777216 ||
                    ul > 0xFFFF || ul == 0)
                    {
                        nInvalid++;
                        continue;
                    }
                    SourcesList->Write( &dwID, sizeof(dwID));
                    SourcesList->Write( &nPort, sizeof(nPort));
                    SourcesList->Write( &dwServerIP, sizeof(dwServerIP));
                    SourcesList->Write( &nServerPort, sizeof(nServerPort));
                    nCount++;
                }
                //ToBegin();:
                SourcesList->Seek(0);
                SourcesList->Write( &nCount, sizeof(nCount));
                //ToBegin();:
                SourcesList->Seek(0);
                if (nCount == 0)
                {
                    delete SourcesList;
                    SourcesList = NULL;
                }
            }
        }
    }
}

CED2KFileLink::~CED2KFileLink()
{
}

void
CED2KFileLink::GetLink(wxString &lnk)
{
    lnk = ("ed2k://|file|");
    lnk += m_name;
    lnk += ("|");
    lnk += m_size;
    lnk += ("|");
    for (int idx = 0 ; idx != 16 ;++idx)
    {
        unsigned int ui1 = m_hash[idx] / 16;
        unsigned int ui2 = m_hash[idx] % 16;
        lnk += static_cast < TCHAR > (ui1 > 9 ? (('0') + ui1): (('A') + (ui1 - 10)));
        lnk += static_cast < TCHAR > (ui2 > 9 ? (('0') + ui2): (('A') + (ui2 - 10)));
    }
    lnk += ("|/");
}

CED2KServerListLink *
CED2KFileLink::GetServerListLink()
{
    return 0;
}

CED2KServerLink *
CED2KFileLink::GetServerLink()
{
    return 0;
}

CED2KFileLink *
CED2KFileLink::GetFileLink()
{
    m_name.Replace("%20", " ");
    return this;
}

CED2KLink::LinkType
CED2KFileLink::GetKind() const
{
    return kFile;
}

//static
CED2KLink *
CED2KLink::CreateLinkFromUrl(const TCHAR *uri)
{
    // Parse pseudo-URI
    const TCHAR *pChArray[7];
    if (uri == 0)
    throw wxString(_T("null ed2k link"));
    TCHAR *pNewString = strdup(uri);
    autoFree liberator(pNewString);
    TCHAR *pCh = pNewString;
    const TCHAR *pStart = pCh;
    int idx = 0;
    for (idx = 0 ; idx < 7 ; idx++) pChArray[idx] = NULL;
    idx = 0;
    while (idx < 7 && ((pCh = strchr(pCh, _T('|'))) != 0))
    {
        pChArray[idx++] = pStart;
        *pCh = 0;
        ++pCh;
        pStart = pCh;
    }
    if ( *pStart != ('/'))
    {
        throw wxString((GetResString(IDS_ERR_BADED2KLINK)));
    }
    if (idx < 3
    || pChArray[0] == 0
    || pChArray[1] == 0
    || pChArray[2] == 0
    //		|| pChArray[3] == 0 // This was preventing ed2k serverlist links from working..
    || strcmp(("ed2k://"), pChArray[0]) != 0
    )
    {
        throw GetResString(IDS_ERR_BADED2KLINK);
    }
    if (strcmp(("file"), pChArray[1]) == 0 &&idx >= 5 &&pChArray[4] != 0)
    {
        return new CED2KFileLink(pChArray[2], pChArray[3], pChArray[4], pChArray[6]);
    }
    else if(strcmp(_T("serverlist"), pChArray[1]) == 0 &&idx == 3)
    {
        return new CED2KServerListLink(pChArray[2]);
    }
    else if(strcmp(_T("server"), pChArray[1]) == 0 &&idx == 4)
    {
        return new CED2KServerLink(pChArray[2], pChArray[3]);
    }
    else
    {
        throw GetResString(IDS_ERR_NOSLLINK);
    }
    return 0;
}

#if 0
//static
CED2KLink *
CED2KLink::CreateLinkFromUrl(const TCHAR *uri)
{
    // Parse pseudo-URI
    const TCHAR *pChArray[7];
    if (uri == 0)
    throw wxString(("null ed2k link"));
    TCHAR *pNewString = strdup(uri);
    autoFree liberator(pNewString);
    TCHAR *pCh = pNewString;
    const TCHAR *pStart = pCh;
    int idx = 0;
    for (idx = 0 ; idx < 7 ; idx++) pChArray[idx] = NULL;
    idx = 0;
    while (idx < 7 && ((pCh = strchr(pCh, ('|'))) != 0))
    {
        pChArray[idx++] = pStart;
        *pCh = 0;
        ++pCh;
        pStart = pCh;
    }
    if ( *pStart != ('/'))
    {
        throw wxString((GetResString(IDS_ERR_BADED2KLINK)));
    }
    if (idx < 3
    || pChArray[0] == 0
    || pChArray[1] == 0
    || pChArray[2] == 0
    || pChArray[3] == 0
    || strcmp(("ed2k://"), pChArray[0]) != 0
    )
    {
        throw GetResString(IDS_ERR_BADED2KLINK);
    }
    if (strcmp(("file"), pChArray[1]) == 0 &&idx == 5 &&pChArray[4] != 0)
    {
        return new CED2KFileLink(pChArray[2], pChArray[3], pChArray[4]);
    }
    else if(strcmp(("serverlist"), pChArray[1]) == 0 &&idx == 3)
    {
        return new CED2KServerListLink(pChArray[2]);
    }
    else if(strcmp(("server"), pChArray[1]) == 0 &&idx == 4)
    {
        return new CED2KServerLink(pChArray[2], pChArray[3]);
    }
    else
    {
        throw GetResString(IDS_ERR_NOSLLINK);
    }
    return 0;
}

#endif

