#include "pageManager.h"

PageManager::PageManager(std::string sock5ProxyOnlyAddress, std::string cookieFilePath)
    : sock5Proxy("socks5://" + sock5ProxyOnlyAddress), cookieFilePath(cookieFilePath)
{
    curl_global_init(CURL_GLOBAL_ALL);
}

PageManager::~PageManager()
{
    curl_global_cleanup();
}

void PageManager::setProxy(std::string ip, int port)
{
    this->sock5Proxy = "socks5://" + ip + ":" + std::to_string(port);
}

void PageManager::setCookieFilePath(std::string path)
{
    this->cookieFilePath = path;
}

void PageManager::setDebugMode(bool status)
{
    this->debugMode = status;
}

//Save reply to string
size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp)
{
    //Function für CURL
    static_cast<std::string*>(userp)->append(static_cast<char*>(contents),size * nmemb);
    return size * nmemb;
}

//Write data to file
static size_t write_data(void *ptr, size_t size, size_t nmemb, void *stream)
{
  size_t written = fwrite(ptr, size, nmemb, reinterpret_cast<FILE *>(stream));
  return written;
}

Reply PageManager::getServerRequest(std::string Url, bool useCookies, std::string data, bool generateCookieFile)
{
    CURL *curl;
    CURLcode res;
    std::string readBuffer;
    char *url;
    std::string returnUrl;

    std::cout << ( "\33[2K\rLade: '" + Url + "'..." ) << std::flush;

    curl = curl_easy_init();
    if(!curl) {
        perror("\33[2K\r => Error: Curl easy init failed");
        return Reply("-1");
    }

    //Settings
    //curl_easy_setopt(curl, CURLOPT_FAILONERROR, true); // html errors to errorcode res

    curl_easy_setopt(curl, CURLOPT_URL, Url.c_str());     //Url für Curl
    curl_easy_setopt(curl, CURLOPT_PROXY, sock5Proxy.c_str() );     //Sock5Proxy für Curl
    curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);     //follows redirection
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);  // Funktion zum Speichern des outputs in einem string
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer);     //Legt die Variable readbuffer fest
    curl_easy_setopt(curl, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:64.0) Gecko/20100101 Firefox/64.0");
    if(useCookies)
        curl_easy_setopt(curl, CURLOPT_COOKIEFILE, cookieFilePath.c_str());
    if(data != "")
        curl_easy_setopt (curl, CURLOPT_POSTFIELDS, data.c_str());
    if(generateCookieFile)
        curl_easy_setopt (curl, CURLOPT_COOKIEJAR, cookieFilePath.c_str());

    int maxTimeout = 10;
    for (int timeout = 1; timeout <= maxTimeout; ++timeout) {
        /* Perform the request, res will get the return code */
        res = curl_easy_perform(curl);
        std::cout << "\33[2K\r" << std::flush;

        if(res != CURLE_OK) {
            if(timeout == maxTimeout) {
                perror((std::string("\33[2K\r => Error: curl_easy_perform() failed: ") + curl_easy_strerror(res)).c_str());
                return Reply("-1");
            } else {
                std::cout << std::string( "\33[2K\r => Warning: Versuch " + std::to_string(timeout) + " von " + std::to_string(maxTimeout) + ": curl_easy_perform() failed: " + curl_easy_strerror(res) )<< std::flush;
                sleep(1);
            }
        } else {
            break;
        }

    }

    //Get Url
    res = curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &url);
    if( res != CURLE_OK || !url ) {
        perror((std::string("\33[2K\r => Error: curl_easy_getinfo failed: ") + curl_easy_strerror(res)).c_str());
        return Reply("-1");
    } else
        returnUrl=url;


    /* always cleanup */ /* Mach den Griff zu, schreib die Kekse! */
    curl_easy_cleanup(curl);

    return Reply(readBuffer, returnUrl);
}


int PageManager::downLoadToFile(std::string filePath, std::string url)
{
    CURL *curl_handle;
    FILE *pagefile;
    CURLcode res;

    /* open the file */
    pagefile = fopen(filePath.c_str(), "wb"); // w == write; b == binäre
    if(!pagefile) {
        perror("Open File filed");
        return 1;
    }

    /* init the curl session */
    curl_handle = curl_easy_init();
    if(!curl_handle) {
        perror("\33[2K\r => Error: Curl easy init failed");
        return 2;
    }

    /* set URL to get here */
    curl_easy_setopt(curl_handle, CURLOPT_URL, url.c_str());

    /* Switch on full protocol/debug output while testing */
    curl_easy_setopt(curl_handle, CURLOPT_VERBOSE, false);

    /* disable progress meter, set to 0L to enable and disable debug output */
    curl_easy_setopt(curl_handle, CURLOPT_NOPROGRESS, false);

    /* send all data to this function  */
    curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, write_data);

    //Sock5Proxy für Curl
    curl_easy_setopt(curl_handle, CURLOPT_PROXY, sock5Proxy.c_str() );

    //User Agent
    curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:64.0) Gecko/20100101 Firefox/64.0");

    /* write the page body to this file handle */
    curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, pagefile);

    //Error 404, for example => not found
    curl_easy_setopt(curl_handle, CURLOPT_FAILONERROR, true);

    int maxTimeOut = 5;
    bool failed = false;
    for (int timeout = 1; timeout <= maxTimeOut; ++timeout) {
        /* get it! */
        if( (res = curl_easy_perform(curl_handle)) != CURLE_OK ) {
            //Wenns fehlschlägt error  ( + wiederversuchen )
            if(timeout == maxTimeOut) {
                perror((std::string("\33[2K\r => Error: curl_easy_perform() failed: ") + curl_easy_strerror(res)).c_str());
                failed = true;
            } else {
                std::cout << std::string( "\33[2K\r => Warning: Versuch " + std::to_string(timeout) + " von " + std::to_string(maxTimeOut) + ": curl_easy_perform() failed: " + curl_easy_strerror(res) )<< std::flush;
                sleep(1);
            }
        } else {
            //Sonst ( wenns functioniert) schleife verlassen
            break;
        }
    }

    /* close the header file */
    if(fclose(pagefile) != 0) {
        perror(" => Error: fclose failed");
        return 4;
    }

    /* cleanup curl stuff */
    curl_easy_cleanup(curl_handle);

    return (failed) ? 10 : 0;
}

int PageManager::login(Account account)
{
    if(debugMode)
        std::cout << " > Melde mit neuem Account an: Email: " << account.Email << " Passowort: " << account.Password << std::endl;

    std::string html = getServerRequest("https://s.to/login", false, std::string("email=" + account.Email + "&password=" + account.Password), true ).html;
    if(html  == "" )
        return 0;
    else if (html.find("Das Feld Email muss eine gültige E-Mail-Adresse enthalten.") != std::string::npos)
        std::cout << " => Error: Login failed: Das Feld Email muss eine gültige E-Mail-Adresse enthalten." << std::endl
                  << "  Email: '" << account.Email << "' Passwort: '" << account.Password << "'" << std::endl;
    else if (html.find("Das Passwort ist nicht korrekt") != std::string::npos)
        std::cout << " => Error: Login failed: Das Passwort ist nicht korrekt." << std::endl
                  << "  Email: '" << account.Email << "' Passwort: '" << account.Password << "'" << std::endl;
    else if (html.find("Ein Account mit dieser E-Mail Adresse wurde nicht gefunden.") != std::string::npos)
        std::cout << " => Error: Login failed: Ein Account mit dieser E-Mail Adresse wurde nicht gefunden." << std::endl
                  << "  Email: '" << account.Email << "' Passwort: '" << account.Password << "'" << std::endl;
    else if(html == "-1")
        return -1;
    else
        std::cout << " => Error: Login failed: Keine Weiterleitung bei Login." << std::endl
                  << "  Email: '" << account.Email << "' Passwort: '" << account.Password << "'" << std::endl;
    return -1;
}

std::string PageManager::getUrlAfterRedirect(std::string Url)
{
    return getServerRequest(Url, true).url;
}

std::string PageManager::checkName(std::string Name)
{
    std::string name = replace(Name, " ", "-");

    std::string html = getServerRequest("https://s.to/serie/stream/" + name).html;
    if(html.find("Die gewünschte Serie wurde nicht gefunden oder ist im Moment deaktiviert.") != std::string::npos) {
        std::cout << "\33[2K\r => Error: Die gewünschte Serie wurde nicht gefunden oder ist im Moment deaktiviert: " << Name << std::endl;
        return "-1";
    } else if (html.find("404 - Seite nicht gefunden") != std::string::npos) {
        std::cout << "\33[2K\r => Error: Ungültiger Name: " << Name << std::endl;
        return "-1";
    } else if (html == "-1"  || html == "") {
        return "-1";
    }
    else {
        std::cout << "\33[2K\r > Name: " << name << std::endl;
        return name;
    }
}

std::string PageManager::getLinks(std::string HTML)
{
    size_t pos = HTML.find("<ul class=\"row\">");
    if(pos == std::string::npos) {
        std::cout << " => Error: Konnte Position von \"" << "<ul class=\"row\">" << " nicht finden" <<std::endl;
        return "";
    }

    HTML.erase(0,pos);

    pos = HTML.find("<script async=");
    if(pos == std::string::npos) {
        std::cout << " => Error: Konnte Position von \"" << "<script async=" << "\" nicht finden" <<std::endl;
        return "";
    }

    HTML.erase(pos,HTML.length() - pos);
    HTML = replace(HTML, "\n", "");     //HTML.replace("\n","").replace("</span>", "\n");
    HTML = replace(HTML, "</span>", "\n");
    HTML = grep(HTML,"href=\"/redirect/");

    std::istringstream iStrStream( HTML + "\n" );
    std::string line, ReturnValue;
    size_t pos2;

    while (getline(iStrStream, line).good()) {
        if(line == "")
            break;
        pos=line.find("data-lang-key=");
        if(pos == std::string::npos) {
            std::cout << " => Error: Konnte Position von \"data-lang-key=\" nicht finden." << std::endl;
            continue;
        }
        //entferne alles bis pos
        line.erase(0,pos);

        pos=line.find("data-link-id=");
        if(pos == std::string::npos) {
            std::cout << " => Error: Konnte Position von \"data-link-id=\" nicht finden." << std::endl;
            continue;
        }
        pos2=line.find("href=\"");
        if(pos2 == std::string::npos) {
            std::cout << " => Error: Konnte Position von 'href=\"' nicht finden." << std::endl;
            continue;
        }
        //Entferne alles von pos bis pos2
        line.erase(pos,pos2-pos);

        pos=line.find("target=");
        if(pos == std::string::npos) {
            std::cout << " => Error: Konnte Position von \"target=\" nicht finden." << std::endl;
            continue;
        }
        pos2=line.find("title=");
        if(pos2 == std::string::npos) {
            std::cout << " => Error: Konnte Position von \"title=\" nicht finden." << std::endl;
            continue;
        }
        //entferne alles von pos bis pos2
        line.erase(pos,pos2-pos);

        pos=line.find("><");
        if(pos == std::string::npos) {
            std::cout << " => Error: Konnte Position von \"><\" nicht finden." << std::endl;
            continue;
        }
        //entferne alles von pos bis zum ende
        line.erase(pos,line.length()-pos);

        line = replace(line, "title=\"Hoster ", "hoster=\"");
        ReturnValue+=line+"\n";
    }

    if(ReturnValue.length() > 0)
        return ReturnValue.erase( ReturnValue.size()-1 , 1);
    else
        return "";

}

std::string PageManager::replace(std::string str, std::string substr1, std::string substr2)
{
    if(substr1 == "")
        return str;
    size_t index = 0;
    for (index = str.find(substr1, index); index != std::string::npos; index = str.find(substr1, index + substr1.length()) )
        str.replace(index, substr1.length(), substr2);
    return str;
}

int PageManager::counterContains(std::string text, std::string substring_with_prozent_i_for_number, int starte_mit_dieser_Zahl)
{
    int i = starte_mit_dieser_Zahl;
    for (; text.find( replace(substring_with_prozent_i_for_number, "%i", std::to_string(i)) ) != std::string::npos; ++i);
    return i-1;
}

std::string PageManager::grep(std::string text, std::string substring, bool IgnoreCaseSensetifity)
{
    std::istringstream iStrStream(text + "\n");
    std::string line, returnValue;
    while( std::getline(iStrStream, line).good() )          //auto start_of_line_position = begin( line );         //auto end_of_line_position = end( line );
        if(line.find(substring) != std::string::npos || ( IgnoreCaseSensetifity && upper_string(line).find(upper_string(substring)) != std::string::npos) )
            returnValue += line + "\n";
    if(returnValue.length() >= 1)
        return returnValue.erase(returnValue.length()-1,1);
    else
        return "";

}

std::string PageManager::upper_string(const std::string &str)
{
    std::string upper;
    transform(str.begin(), str.end(), std::back_inserter(upper), toupper);
    return upper;
}

size_t PageManager::getDate()
{
    std::time_t now = std::time(nullptr);
    struct tm *tm_now = localtime(&now);
    return static_cast<size_t>( static_cast<double>(1900 + tm_now->tm_year) * 365.24220 +
                                static_cast<double>(tm_now->tm_mon +1) * 30.43685 + tm_now->tm_mday);
}

std::string PageManager::getExePath()
{

#ifdef __linux__

    char buff[PATH_MAX];
    ssize_t len = ::readlink("/proc/self/exe", buff, sizeof(buff)-1);
    if (len == -1) {
        perror("Readlink proc self exe failed");
        return "";
    } else {
        buff[len] = '\0';
        return std::string(buff);
    }

#endif
#ifdef _WIN32
    wchar_t buffer[MAX_PATH + 1];
    GetModuleFileName( nullptr, buffer, MAX_PATH );
    std::wstring wstr(buffer);
    return std::string(wstr.begin(), wstr.end());
#endif

}

int PageManager::compareVersions(std::string Version1, std::string Version2)
{
    std::string StringList[2] = { ( Version1 + "." ), ( Version2 + "." ) };
    int Versions[2][3];
    size_t pos;

    //Für beide Versionen:
    for (int n = 0; n < 2; ++n) {
        //Für 3 Punkte:
        for (int i = 0; i < 3; ++i) {
            //Wenn kein Punkt gefunden werden konnte => Error:
            if( (pos = StringList[n].find(".")) == std::string::npos) {
                std::cout << " => Error: Ungültige ProgrammVersion: '" << ( (n == 0) ? Version1 : Version2 ) << "'" << std::endl;
                return -1;
            //wenn punkt gefunden werden konnte
            } else {
                //Wenn der Teilstring keine Zahl ist error:
                if( !isNumber(StringList[n].substr(0, pos)) ) {
                    std::cout << " => Error: Ungültige ProgrammVersion: (Keine Zahl): '" << ( (n == 0) ? Version1 : Version2 ) << "'" << std::endl;
                    return -1;
                } else {
                    //Sonst speicher Zahl in array und entferne den anfang des strings
                    Versions[n][i] = atoi(StringList[n].substr(0, pos).c_str());
                    StringList[n].erase(0, pos + 1);
                }
            }
        }
    }

    for (int i = 0; i < 3; ++i) {
        if(Versions[0][i] > Versions[1][i]) {
            return 2;
        } else if(Versions[0][i] < Versions[1][i]) {
            return 1;
        }
    }
    return 0;


}

int PageManager::writeToFile(std::string path, std::string text)
{
    if(path == "")
        return 0;
    std::ofstream of;
    of.open(path, std::ios::out | std::ios::app);
    if(!of.is_open()) {
        perror(" => Error: Konnte Output Datei nicht öffnen");
        return -1;
    }
    of << text << std::endl;
    of.close();
    return 0;
}

std::string PageManager::chooseHosterLink(std::string HosterList, std::string Hoster_with_Highst_Priority_at_First, std::string languages_with_highst_priority_at_first)
{
    std::istringstream SListLang  ( replace( languages_with_highst_priority_at_first, ",", "\n") + "\n" );
    std::istringstream SListHoster( replace( Hoster_with_Highst_Priority_at_First,    ",", "\n") + "\n" );

    int LangId = 0;
    size_t pos = 0;
    std::string LanguageSortedHoster, Line, langId, hoster;
    //QTextStream stream();

    //Für jede Sprache:
    while (getline(SListLang, langId).good()) {

        if ( langId == "GerDub" )
            LangId=1;
        else if ( langId == "Eng" )
            LangId=2;
        else if ( langId == "GerSub" )
            LangId=3;
        else {
            std::cout << " => Error: Unbekannte Sprache: " << langId << std::endl;
            continue;
        }

        //Liste aller Links mit der Sprache des durchgangs der schleife
        LanguageSortedHoster = grep(HosterList, ( "data-lang-key=\"" + std::to_string(LangId) + "\"" ) );
        //std::cout << "Alle Folgen mi der Sprache " << langId << ":\n'" << LanguageSortedHoster << "'" << std::endl;

        //Wenn keine Links zu der Sprache gefunden worden sind
        if(LanguageSortedHoster == "")
            continue;

        //Für jeden Angegebenen Hoster:
        while (getline(SListHoster, hoster).good()) {
            //Wenn es den hoster bei dieser prache nicht gibt, wähle nächsten
            if(LanguageSortedHoster.find("hoster=\"" + hoster + "\"") == std::string::npos) {
                //std::cout << "Hoster " << hoster << " gibt es bei der sprache" << langId << " nicht " << std::endl;
                continue;
            }
            Line = grep(LanguageSortedHoster, ("hoster=\"" + hoster + "\"" ) );
            pos = Line.find("href=\"");
            if(pos == std::string::npos) {
                std::cout << " => Error: Konnte 'href=\"' in chooseHosterLink() nicht finden." << std::endl;
                continue;
            }
            Line.erase(0, pos + 6/*static_cast<int>(strlen("href=\""))*/);

            pos = Line.find("\"");
            if(pos == std::string::npos) {
                std::cout << " => Error: Konnte '\"' in chooseHosterLink() nicht finden." << std::endl;
                continue;
            }

            return Line.erase(pos, Line.length()-pos);
        }

    }
    return "";
}
