/*
 * Copyright (C) 2009-2010 Big Muscle, http://strongdc.sf.net
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include "stdafx.h"
#include "BootstrapManager.h"
#include "Constants.h"
#include "DHT.h"
#include "SearchManager.h"
#include "dcpp/AdcCommand.h"
#include "dcpp/ClientManager.h"
#include "dcpp/HttpConnection.h"
#include "dcpp/LogManager.h"
#include <zlib.h>

namespace dht
{

    BootstrapManager::BootstrapManager(void)
    {
        httpConnection.addListener(this);
    }

    BootstrapManager::~BootstrapManager(void)
    {
        httpConnection.removeListener(this);
    }

    void BootstrapManager::bootstrap()
    {
        if(bootstrapNodes.empty())
        {
            LogManager::getInstance()->message("DHT bootstrapping started");

            // TODO: make URL settable
            string url = BOOTSTRAP_URL "?cid=" + ClientManager::getInstance()->getMe()->getCID().toBase32() + "&encryption=1";

            // store only active nodes to database
            if(ClientManager::getInstance()->isActive(Util::emptyString))
            {
                url += "&u4=" + Util::toString(DHT::getInstance()->getPort());
            }

            httpConnection.setCoralizeState(HttpConnection::CST_NOCORALIZE);
            httpConnection.downloadFile(url);
        }
    }

    void BootstrapManager::on(HttpConnectionListener::Data, HttpConnection*, const uint8_t* buf, size_t len) throw()
    {
        nodesXML += string((const char*)buf, len);
    }

    #define BUFSIZE 16384
    void BootstrapManager::on(HttpConnectionListener::Complete, HttpConnection*, string const&, bool /*fromCoral*/) throw()
    {
        if(!nodesXML.empty())
        {
            try
            {
                uLongf destLen = BUFSIZE;
                boost::scoped_array<uint8_t> destBuf;

                // decompress incoming packet
                int result;

                do
                {
                    destLen *= 2;
                    destBuf.reset(new uint8_t[destLen]);

                    result = uncompress(&destBuf[0], &destLen, (Bytef*)nodesXML.data(), nodesXML.length());
                }
                while (result == Z_BUF_ERROR);

                if(result != Z_OK)
                {
                    // decompression error!!!
                    throw Exception("Decompress error.");
                }

                SimpleXML remoteXml;
                remoteXml.fromXML(string((char*)&destBuf[0], destLen));
                remoteXml.stepIn();

                while(remoteXml.findChild("Node"))
                {
                    CID cid     = CID(remoteXml.getChildAttrib("CID"));
                    string i4   = remoteXml.getChildAttrib("I4");
                    string u4   = remoteXml.getChildAttrib("U4");

                    addBootstrapNode(i4, static_cast<uint16_t>(Util::toInt(u4)), cid, UDPKey());
                }

                remoteXml.stepOut();
            }
            catch(Exception& e)
            {
                LogManager::getInstance()->message("DHT bootstrap error: " + e.getError());
            }
        }
    }

    void BootstrapManager::on(HttpConnectionListener::Failed, HttpConnection*, const string& aLine) throw()
    {
        LogManager::getInstance()->message("DHT bootstrap error: " + aLine);
    }

    void BootstrapManager::addBootstrapNode(const string& ip, uint16_t udpPort, const CID& targetCID, const UDPKey& udpKey)
    {
        BootstrapNode node = { ip, udpPort, targetCID, udpKey };
        bootstrapNodes.push_back(node);
    }

    void BootstrapManager::process()
    {
        Lock l(cs);
        if(!bootstrapNodes.empty())
        {
            // send bootstrap request
            AdcCommand cmd(AdcCommand::CMD_GET, AdcCommand::TYPE_UDP);
            cmd.addParam("nodes");
            cmd.addParam("dht.xml");

            const BootstrapNode& node = bootstrapNodes.front();

            CID key;
            // if our external IP changed from the last time, we can't encrypt packet with this key
            // this won't probably work now
            if(DHT::getInstance()->getLastExternalIP() == node.udpKey.ip)
                key = node.udpKey.key;

            DHT::getInstance()->send(cmd, node.ip, node.udpPort, node.cid, key);

            bootstrapNodes.pop_front();
        }
    }

}
