/*
 * Bittorrent Client using Qt and libtorrent.
 * Copyright (C) 2015  Vladimir Golovnev <glassez@yandex.ru>
 * Copyright (C) 2010  Christophe Dumez <chris@qbittorrent.org>
 *
 * 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.
 *
 * In addition, as a special exception, the copyright holders give permission to
 * link this program with the OpenSSL project's "OpenSSL" library (or with
 * modified versions of it that use the same license as the "OpenSSL" library),
 * and distribute the linked executables. You must obey the GNU General Public
 * License in all respects for all of the code used other than "OpenSSL".  If you
 * modify file(s), you may extend this exception to your version of the file(s),
 * but you are not obligated to do so. If you do not wish to do so, delete this
 * exception statement from your version.
 */

#include "geoipmanager.h"

#include <QDateTime>
#include <QDebug>
#include <QDir>
#include <QFile>
#include <QHostAddress>

#include "base/logger.h"
#include "base/preferences.h"
#include "base/profile.h"
#include "base/utils/fs.h"
#include "base/utils/gzip.h"
#include "downloadhandler.h"
#include "downloadmanager.h"
#include "private/geoipdatabase.h"

static const char DATABASE_URL[] = "https://geolite.maxmind.com/download/geoip/database/GeoLite2-Country.mmdb.gz";
static const char GEOIP_FOLDER[] = "GeoIP";
static const char GEOIP_FILENAME[] = "GeoLite2-Country.mmdb";
static const int UPDATE_INTERVAL = 30; // Days between database updates

using namespace Net;

// GeoIPManager

GeoIPManager *GeoIPManager::m_instance = nullptr;

GeoIPManager::GeoIPManager()
    : m_enabled(false)
    , m_geoIPDatabase(nullptr)
{
    configure();
    connect(Preferences::instance(), SIGNAL(changed()), SLOT(configure()));
}

GeoIPManager::~GeoIPManager()
{
    if (m_geoIPDatabase)
        delete m_geoIPDatabase;
}

void GeoIPManager::initInstance()
{
    if (!m_instance)
        m_instance = new GeoIPManager;
}

void GeoIPManager::freeInstance()
{
    if (m_instance) {
        delete m_instance;
        m_instance = nullptr;
    }
}

GeoIPManager *GeoIPManager::instance()
{
    return m_instance;
}

void GeoIPManager::loadDatabase()
{
    if (m_geoIPDatabase) {
        delete m_geoIPDatabase;
        m_geoIPDatabase = nullptr;
    }

    QString filepath = Utils::Fs::expandPathAbs(
                QString("%1%2/%3").arg(specialFolderLocation(SpecialFolder::Data))
                .arg(GEOIP_FOLDER).arg(GEOIP_FILENAME));

    QString error;
    m_geoIPDatabase = GeoIPDatabase::load(filepath, error);
    if (m_geoIPDatabase)
        Logger::instance()->addMessage(tr("GeoIP database loaded. Type: %1. Build time: %2.")
                                       .arg(m_geoIPDatabase->type()).arg(m_geoIPDatabase->buildEpoch().toString()),
                                       Log::INFO);
    else
        Logger::instance()->addMessage(tr("Couldn't load GeoIP database. Reason: %1").arg(error), Log::WARNING);

    manageDatabaseUpdate();
}

void GeoIPManager::manageDatabaseUpdate()
{
    if (!m_geoIPDatabase || (m_geoIPDatabase->buildEpoch().daysTo(QDateTime::currentDateTimeUtc()) >= UPDATE_INTERVAL))
        downloadDatabaseFile();
}

void GeoIPManager::downloadDatabaseFile()
{
    DownloadHandler *handler = DownloadManager::instance()->downloadUrl(DATABASE_URL);
    connect(handler, SIGNAL(downloadFinished(QString, QByteArray)), SLOT(downloadFinished(QString, QByteArray)));
    connect(handler, SIGNAL(downloadFailed(QString, QString)), SLOT(downloadFailed(QString, QString)));
}

QString GeoIPManager::lookup(const QHostAddress &hostAddr) const
{
    if (m_enabled && m_geoIPDatabase)
        return m_geoIPDatabase->lookup(hostAddr);

    return QString();
}

QString GeoIPManager::CountryName(const QString &countryISOCode)
{
    static QHash<QString, QString> countries;
    static bool initialized = false;

    // ISO 3166-1 alpha-2 codes
    // http://www.iso.org/iso/home/standards/country_codes/country_names_and_code_elements_txt-temp.htm
    if (!initialized) {
        // Officially assigned
        countries["AD"] = tr("Andorra");
        countries["AE"] = tr("United Arab Emirates");
        countries["AF"] = tr("Afghanistan");
        countries["AG"] = tr("Antigua and Barbuda");
        countries["AI"] = tr("Anguilla");
        countries["AL"] = tr("Albania");
        countries["AM"] = tr("Armenia");
        countries["AO"] = tr("Angola");
        countries["AQ"] = tr("Antarctica");
        countries["AR"] = tr("Argentina");
        countries["AS"] = tr("American Samoa");
        countries["AT"] = tr("Austria");
        countries["AU"] = tr("Australia");
        countries["AW"] = tr("Aruba");
        countries["AX"] = tr("Aland Islands");
        countries["AZ"] = tr("Azerbaijan");
        countries["BA"] = tr("Bosnia and Herzegovina");
        countries["BB"] = tr("Barbados");
        countries["BD"] = tr("Bangladesh");
        countries["BE"] = tr("Belgium");
        countries["BF"] = tr("Burkina Faso");
        countries["BG"] = tr("Bulgaria");
        countries["BH"] = tr("Bahrain");
        countries["BI"] = tr("Burundi");
        countries["BJ"] = tr("Benin");
        countries["BL"] = tr("Saint Barthelemy");
        countries["BM"] = tr("Bermuda");
        countries["BN"] = tr("Brunei Darussalam");
        countries["BO"] = tr("Bolivia, Plurinational State of");
        countries["BQ"] = tr("Bonaire, Sint Eustatius and Saba");
        countries["BR"] = tr("Brazil");
        countries["BS"] = tr("Bahamas");
        countries["BT"] = tr("Bhutan");
        countries["BV"] = tr("Bouvet Island");
        countries["BW"] = tr("Botswana");
        countries["BY"] = tr("Belarus");
        countries["BZ"] = tr("Belize");
        countries["CA"] = tr("Canada");
        countries["CC"] = tr("Cocos (Keeling) Islands");
        countries["CD"] = tr("Congo, The Democratic Republic of the");
        countries["CF"] = tr("Central African Republic");
        countries["CG"] = tr("Congo");
        countries["CH"] = tr("Switzerland");
        countries["CI"] = tr("Cote d'Ivoire");
        countries["CK"] = tr("Cook Islands");
        countries["CL"] = tr("Chile");
        countries["CM"] = tr("Cameroon");
        countries["CN"] = tr("China");
        countries["CO"] = tr("Colombia");
        countries["CR"] = tr("Costa Rica");
        countries["CU"] = tr("Cuba");
        countries["CV"] = tr("Cape Verde");
        countries["CW"] = tr("Curacao");
        countries["CX"] = tr("Christmas Island");
        countries["CY"] = tr("Cyprus");
        countries["CZ"] = tr("Czech Republic");
        countries["DE"] = tr("Germany");
        countries["DJ"] = tr("Djibouti");
        countries["DK"] = tr("Denmark");
        countries["DM"] = tr("Dominica");
        countries["DO"] = tr("Dominican Republic");
        countries["DZ"] = tr("Algeria");
        countries["EC"] = tr("Ecuador");
        countries["EE"] = tr("Estonia");
        countries["EG"] = tr("Egypt");
        countries["EH"] = tr("Western Sahara");
        countries["ER"] = tr("Eritrea");
        countries["ES"] = tr("Spain");
        countries["ET"] = tr("Ethiopia");
        countries["FI"] = tr("Finland");
        countries["FJ"] = tr("Fiji");
        countries["FK"] = tr("Falkland Islands (Malvinas)");
        countries["FM"] = tr("Micronesia, Federated States of");
        countries["FO"] = tr("Faroe Islands");
        countries["FR"] = tr("France");
        countries["GA"] = tr("Gabon");
        countries["GB"] = tr("United Kingdom");
        countries["GD"] = tr("Grenada");
        countries["GE"] = tr("Georgia");
        countries["GF"] = tr("French Guiana");
        countries["GG"] = tr("Guernsey");
        countries["GH"] = tr("Ghana");
        countries["GI"] = tr("Gibraltar");
        countries["GL"] = tr("Greenland");
        countries["GM"] = tr("Gambia");
        countries["GN"] = tr("Guinea");
        countries["GP"] = tr("Guadeloupe");
        countries["GQ"] = tr("Equatorial Guinea");
        countries["GR"] = tr("Greece");
        countries["GS"] = tr("South Georgia and the South Sandwich Islands");
        countries["GT"] = tr("Guatemala");
        countries["GU"] = tr("Guam");
        countries["GW"] = tr("Guinea-Bissau");
        countries["GY"] = tr("Guyana");
        countries["HK"] = tr("Hong Kong");
        countries["HM"] = tr("Heard Island and McDonald Islands");
        countries["HN"] = tr("Honduras");
        countries["HR"] = tr("Croatia");
        countries["HT"] = tr("Haiti");
        countries["HU"] = tr("Hungary");
        countries["ID"] = tr("Indonesia");
        countries["IE"] = tr("Ireland");
        countries["IL"] = tr("Israel");
        countries["IM"] = tr("Isle of Man");
        countries["IN"] = tr("India");
        countries["IO"] = tr("British Indian Ocean Territory");
        countries["IQ"] = tr("Iraq");
        countries["IR"] = tr("Iran, Islamic Republic of");
        countries["IS"] = tr("Iceland");
        countries["IT"] = tr("Italy");
        countries["JE"] = tr("Jersey");
        countries["JM"] = tr("Jamaica");
        countries["JO"] = tr("Jordan");
        countries["JP"] = tr("Japan");
        countries["KE"] = tr("Kenya");
        countries["KG"] = tr("Kyrgyzstan");
        countries["KH"] = tr("Cambodia");
        countries["KI"] = tr("Kiribati");
        countries["KM"] = tr("Comoros");
        countries["KN"] = tr("Saint Kitts and Nevis");
        countries["KP"] = tr("Korea, Democratic People's Republic of");
        countries["KR"] = tr("Korea, Republic of");
        countries["KW"] = tr("Kuwait");
        countries["KY"] = tr("Cayman Islands");
        countries["KZ"] = tr("Kazakhstan");
        countries["LA"] = tr("Lao People's Democratic Republic");
        countries["LB"] = tr("Lebanon");
        countries["LC"] = tr("Saint Lucia");
        countries["LI"] = tr("Liechtenstein");
        countries["LK"] = tr("Sri Lanka");
        countries["LR"] = tr("Liberia");
        countries["LS"] = tr("Lesotho");
        countries["LT"] = tr("Lithuania");
        countries["LU"] = tr("Luxembourg");
        countries["LV"] = tr("Latvia");
        countries["LY"] = tr("Libya");
        countries["MA"] = tr("Morocco");
        countries["MC"] = tr("Monaco");
        countries["MD"] = tr("Moldova, Republic of");
        countries["ME"] = tr("Montenegro");
        countries["MF"] = tr("Saint Martin (French part)");
        countries["MG"] = tr("Madagascar");
        countries["MH"] = tr("Marshall Islands");
        countries["MK"] = tr("Macedonia, The Former Yugoslav Republic of");
        countries["ML"] = tr("Mali");
        countries["MM"] = tr("Myanmar");
        countries["MN"] = tr("Mongolia");
        countries["MO"] = tr("Macao");
        countries["MP"] = tr("Northern Mariana Islands");
        countries["MQ"] = tr("Martinique");
        countries["MR"] = tr("Mauritania");
        countries["MS"] = tr("Montserrat");
        countries["MT"] = tr("Malta");
        countries["MU"] = tr("Mauritius");
        countries["MV"] = tr("Maldives");
        countries["MW"] = tr("Malawi");
        countries["MX"] = tr("Mexico");
        countries["MY"] = tr("Malaysia");
        countries["MZ"] = tr("Mozambique");
        countries["NA"] = tr("Namibia");
        countries["NC"] = tr("New Caledonia");
        countries["NE"] = tr("Niger");
        countries["NF"] = tr("Norfolk Island");
        countries["NG"] = tr("Nigeria");
        countries["NI"] = tr("Nicaragua");
        countries["NL"] = tr("Netherlands");
        countries["NO"] = tr("Norway");
        countries["NP"] = tr("Nepal");
        countries["NR"] = tr("Nauru");
        countries["NU"] = tr("Niue");
        countries["NZ"] = tr("New Zealand");
        countries["OM"] = tr("Oman");
        countries["PA"] = tr("Panama");
        countries["PE"] = tr("Peru");
        countries["PF"] = tr("French Polynesia");
        countries["PG"] = tr("Papua New Guinea");
        countries["PH"] = tr("Philippines");
        countries["PK"] = tr("Pakistan");
        countries["PL"] = tr("Poland");
        countries["PM"] = tr("Saint Pierre and Miquelon");
        countries["PN"] = tr("Pitcairn");
        countries["PR"] = tr("Puerto Rico");
        countries["PS"] = tr("Palestine, State of");
        countries["PT"] = tr("Portugal");
        countries["PW"] = tr("Palau");
        countries["PY"] = tr("Paraguay");
        countries["QA"] = tr("Qatar");
        countries["RE"] = tr("Reunion");
        countries["RO"] = tr("Romania");
        countries["RS"] = tr("Serbia");
        countries["RU"] = tr("Russian Federation");
        countries["RW"] = tr("Rwanda");
        countries["SA"] = tr("Saudi Arabia");
        countries["SB"] = tr("Solomon Islands");
        countries["SC"] = tr("Seychelles");
        countries["SD"] = tr("Sudan");
        countries["SE"] = tr("Sweden");
        countries["SG"] = tr("Singapore");
        countries["SH"] = tr("Saint Helena, Ascension and Tristan da Cunha");
        countries["SI"] = tr("Slovenia");
        countries["SJ"] = tr("Svalbard and Jan Mayen");
        countries["SK"] = tr("Slovakia");
        countries["SL"] = tr("Sierra Leone");
        countries["SM"] = tr("San Marino");
        countries["SN"] = tr("Senegal");
        countries["SO"] = tr("Somalia");
        countries["SR"] = tr("Suriname");
        countries["SS"] = tr("South Sudan");
        countries["ST"] = tr("Sao Tome and Principe");
        countries["SV"] = tr("El Salvador");
        countries["SX"] = tr("Sint Maarten (Dutch part)");
        countries["SY"] = tr("Syrian Arab Republic");
        countries["SZ"] = tr("Swaziland");
        countries["TC"] = tr("Turks and Caicos Islands");
        countries["TD"] = tr("Chad");
        countries["TF"] = tr("French Southern Territories");
        countries["TG"] = tr("Togo");
        countries["TH"] = tr("Thailand");
        countries["TJ"] = tr("Tajikistan");
        countries["TK"] = tr("Tokelau");
        countries["TL"] = tr("Timor-Leste");
        countries["TM"] = tr("Turkmenistan");
        countries["TN"] = tr("Tunisia");
        countries["TO"] = tr("Tonga");
        countries["TR"] = tr("Turkey");
        countries["TT"] = tr("Trinidad and Tobago");
        countries["TV"] = tr("Tuvalu");
        countries["TW"] = tr("Taiwan");
        countries["TZ"] = tr("Tanzania, United Republic of");
        countries["UA"] = tr("Ukraine");
        countries["UG"] = tr("Uganda");
        countries["UM"] = tr("United States Minor Outlying Islands");
        countries["US"] = tr("United States");
        countries["UY"] = tr("Uruguay");
        countries["UZ"] = tr("Uzbekistan");
        countries["VA"] = tr("Holy See (Vatican City State)");
        countries["VC"] = tr("Saint Vincent and the Grenadines");
        countries["VE"] = tr("Venezuela, Bolivarian Republic of");
        countries["VG"] = tr("Virgin Islands, British");
        countries["VI"] = tr("Virgin Islands, U.S.");
        countries["VN"] = tr("Viet Nam");
        countries["VU"] = tr("Vanuatu");
        countries["WF"] = tr("Wallis and Futuna");
        countries["WS"] = tr("Samoa");
        countries["YE"] = tr("Yemen");
        countries["YT"] = tr("Mayotte");
        countries["ZA"] = tr("South Africa");
        countries["ZM"] = tr("Zambia");
        countries["ZW"] = tr("Zimbabwe");

        countries[QString()] = tr("N/A");
        initialized = true;
    }

    return countries.value(countryISOCode, tr("N/A"));
}

void GeoIPManager::configure()
{
    const bool enabled = Preferences::instance()->resolvePeerCountries();
    if (m_enabled != enabled) {
        m_enabled = enabled;
        if (m_enabled && !m_geoIPDatabase) {
            loadDatabase();
        }
        else if (!m_enabled && m_geoIPDatabase) {
            delete m_geoIPDatabase;
            m_geoIPDatabase = nullptr;
        }
    }
}

void GeoIPManager::downloadFinished(const QString &url, QByteArray data)
{
    Q_UNUSED(url);

    bool ok = false;
    data = Utils::Gzip::decompress(data, &ok);
    if (!ok) {
        Logger::instance()->addMessage(tr("Could not decompress GeoIP database file."), Log::WARNING);
        return;
    }

    QString error;
    GeoIPDatabase *geoIPDatabase = GeoIPDatabase::load(data, error);
    if (geoIPDatabase) {
        if (!m_geoIPDatabase || (geoIPDatabase->buildEpoch() > m_geoIPDatabase->buildEpoch())) {
            if (m_geoIPDatabase)
                delete m_geoIPDatabase;
            m_geoIPDatabase = geoIPDatabase;
            Logger::instance()->addMessage(tr("GeoIP database loaded. Type: %1. Build time: %2.")
                                           .arg(m_geoIPDatabase->type()).arg(m_geoIPDatabase->buildEpoch().toString()),
                                           Log::INFO);
            QString targetPath = Utils::Fs::expandPathAbs(
                        specialFolderLocation(SpecialFolder::Data) + GEOIP_FOLDER);
            if (!QDir(targetPath).exists())
                QDir().mkpath(targetPath);
            QFile targetFile(QString("%1/%2").arg(targetPath).arg(GEOIP_FILENAME));
            if (!targetFile.open(QFile::WriteOnly) || (targetFile.write(data) == -1)) {
                Logger::instance()->addMessage(
                            tr("Couldn't save downloaded GeoIP database file."), Log::WARNING);
            }
            else {
                Logger::instance()->addMessage(tr("Successfully updated GeoIP database."), Log::INFO);
            }
        }
        else {
            delete geoIPDatabase;
        }
    }
    else {
        Logger::instance()->addMessage(tr("Couldn't load GeoIP database. Reason: %1").arg(error), Log::WARNING);
    }
}

void GeoIPManager::downloadFailed(const QString &url, const QString &reason)
{
    Q_UNUSED(url);
    Logger::instance()->addMessage(tr("Couldn't download GeoIP database file. Reason: %1").arg(reason), Log::WARNING);
}
