//============================================================================
// Name        : myDownload.cpp
// Author      : nick
// Version     :
// Copyright   : Your copyright notice
// Description : Hello World in C++, Ansi-style
//============================================================================

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <fstream>
#include <string>
#include <openssl/md5.h>
#include <vector>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

// unsigned char *MD5(const unsigned char *d, unsigned long n,
//                  unsigned char *md);
//
// int MD5_Init(MD5_CTX *c);
// int MD5_Update(MD5_CTX *c, const void *data,
//                  unsigned long len);
// int MD5_Final(unsigned char *md, MD5_CTX *c);
using namespace std;

string calcMd5(const string& strFileName)
{
    unsigned char c[MD5_DIGEST_LENGTH];

    int i;
    FILE *inFile = fopen (strFileName.c_str(), "rb");
    MD5_CTX mdContext;
    int bytes;
    unsigned char data[1024];

    if (inFile == NULL) {
        printf ("%s can't be opened.\n", strFileName.c_str());
        return string();
    }

    MD5_Init (&mdContext);
    while ((bytes = fread (data, 1, 1024, inFile)) != 0)
        MD5_Update (&mdContext, data, bytes);
    MD5_Final (c,&mdContext);
    string result;
    for(i = 0; i < MD5_DIGEST_LENGTH; i++)
    {
        char buf[16];
        sprintf(buf, "%02x", c[i]);
        result += buf;
    }
    //printf (" %s\n", strFileName.c_str());
    fclose (inFile);
    return result;
}

string extractName(const string& str)
{
    size_t pos = str.find_first_of(' ');
    return str.substr(0, pos);
}

string extractNameByPatch(const string& str)
{
    size_t pos = str.find(" Patch ");
    return str.substr(0, pos);
}

struct DownloadItem
{
    string name;
    string file;
    string url;
    string md5;
};

bool verifyFile(const string& strFileName, const string& strMd5)
{
    string strPath = "./";
    strPath += strFileName;
    struct stat st;
    if (stat(strPath.c_str(), &st) == 0)
    {
        string str = calcMd5(strPath);
        if (strMd5.compare(str) == 0)
        {
            return true;
        }
    }
    return false;
}

bool downloadFile(const string& strUrl, const string& strFileName)
{
    string cmd = "wget ";
    cmd += strUrl;
    cmd += " -O ";
    cmd += strFileName;

    int ret = 0;
    if ((ret = system(cmd.c_str())) != -1)
    {
        int code = WEXITSTATUS(ret);

        cout << "download successful for " << strUrl << "exit code: " << code << endl;
        return true;
    }
    else
    {
        return false;
    }
}
typedef bool (*VerifyNameFuncType)(const string&);
typedef string (*ExtractNameCallback)(const string&);

bool verifyNameByParenthese(const string& str)
{
    return str.find('(') != string::npos && str.find(')') != string::npos;
}

bool verifyNameByPatch(const string& str)
{
    return str.find(" Patch ")!= string::npos;
}

bool listReader(const string& strListName, vector<DownloadItem>& itemList, VerifyNameFuncType verifyNameCallback, ExtractNameCallback extractNameCallback)
{
    ifstream in(strListName.c_str());
    string str;

    DownloadItem item;
    while (!getline(in, str).eof())
    {
        if (str.size() > 0)
        {
            if (verifyNameCallback(str))
            {
                item.name = extractNameCallback(str);
                cout << "name: " << item.name << endl;
                continue;
            }

            size_t pos;
            const string DownloadTag = "Download: ";
            if ((pos=str.find(DownloadTag)) != string::npos)
            {
                item.url = str.substr(pos + DownloadTag.size());
                cout << "download: " << item.url << endl;
                size_t start = item.url.find_last_of('/');
                if (start != string::npos)
                {
                    item.file = item.url.substr(start+1);
                }
                continue;
            }
            const string MD5Tag = "MD5 sum: ";
            if ((pos=str.find(MD5Tag)) != string::npos)
            {
                item.md5 = str.substr(pos + MD5Tag.size());
                cout << "MD5: " <<  item.md5 << endl;
                itemList.push_back(item);
                continue;
            }
        }
    }
    return itemList.size() > 0;
}

int downloadTool(const vector<DownloadItem>& itemList)
{
    //cout << calcMd5("list.txt") << endl;

	for (size_t i = 0; i < itemList.size(); i ++)
	{
	    const DownloadItem& item = itemList[i];

	    if (!verifyFile(item.file, item.md5))
	    {
	        if (downloadFile(item.url, item.file))
	        {
	            if (!verifyFile(item.file, item.md5))
	            {
	                cout << "verifying ... error... break..." << endl;
	                break;
	            }
	        }
	        else
	        {
	            cout << "*******************downloading error *******************" << endl << item.url << endl;
	        }
	    }
	}
	return 0;
}

void downloadSoftware()
{
    vector<DownloadItem> itemList;
    if (listReader("list.txt", itemList, verifyNameByParenthese, extractName))
    {
        downloadTool(itemList);
    }
}

void downloadPatch()
{
    vector<DownloadItem> itemList;
    if (listReader("patchList.txt", itemList, verifyNameByPatch, extractNameByPatch))
    {
        downloadTool(itemList);
    }
}

int main()
{

    downloadPatch();
    return 0;
}

