/*
 * myListTool.cpp
 *
 *  Created on: Jul 9, 2014
 *      Author: nick
 */
#include <string>
#include <cstdio>
#include <fstream>
#include <iostream>
#include <sstream>
#include <map>
#include <vector>
#include <sys/types.h>
#include <dirent.h>
#include <sys/stat.h>
#include <unistd.h>
#include <cstdlib>


using namespace std;

typedef multimap<string, string> StringMap;
typedef vector<pair<string, string> > StringVector;

const int FirstColumnWidth = 80;
const int SecondColumnWidth = 200;
const int ThirdColumnWidth = 600;

string g_prefix = "http://www.staroceans.org";
string g_GeneratedTree = "generatedTrees";

string targetFileName = "fileLists.html";
string targetFileName2 = "fileVector.html";

string header1 =
		"<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"><title>My All File List</title>";
string header2 =
		"<meta name=\"GENERATOR\" content=\"Microsoft FrontPage 6.0\"></head>  <body><p><font color=\"#FF0000\">";
string header3 =
		"<font color=\"#FF0000\"><font size=\"7\">All File List</font></p>";

string comment1 =
		"This is a list of all files in this website and to make it easy for searching, the they are sorted by file name"
				" and here is <a href=\"";
string comment2 =
		"/myListTool.html\"> the source code </a> of file how it is generated. And the most recent git source is here:"
		"git clone http://www.staroceans.org/myprojects/myLibS3/repo.git\n"
		"git clone http://www.staroceans.org/myprojects/libs3-2/repo.git\n"
		"";


string tableHeader1 =
		"<table border=\"1\" cellpadding=\"5\" cellspacing=\"5\" width=\"1200\" table-layout=\"fixed\""
				"   <colgroup>"
				"<col style=\"width: 50;\">"
				"<col style=\"width: 300;\">"
				"<col style=\"width: 1200;\">"
				"</colgroup>>";
string tableHeader2 =
		"<tr> <th> file index </th>  <th> file name </th>  <th> file path </th> </tr>";
string bottom = "</table></body></html>";


string tableColumn1 = "<tr> <td style=\"width: 50;word-wrap:break-word;max-width:50px;\">";
string tableColumn2 = "</td>  <td style=\"width: 350;word-wrap:break-word;max-width:250px;\" > <a href=\"";
string tableColumn3 = "\">";
string tableColumn4 = "</a> </td> <td style=\"width: 1200;word-wrap:break-word;max-width:500px;\">";
string tableColumn5 = "</td></tr>";

struct FileStruct
{
    string name;
    string url;
    string size;
    string path;
};

struct DirStruct
{
    string name;
    string url;
    string path;
    vector<FileStruct> files;
    vector<DirStruct*> dirs;
};



string urlEncode(const string& str)
{
	string result;
	for (size_t i = 0; i < str.size(); i++)
	{
		if ((str[i] >= '0' && str[i] <= '9') || (str[i] >= 'A' && str[i] <= 'Z')
				|| (str[i] >= 'a' && str[i] <= 'z'))
		{
			result += str[i];
		}
		else
		{
			const unsigned char MASK1 = 0xF0;
			const unsigned char MASK2 = 0x0F;
			unsigned char ch;

			switch (str[i])
			{
			case '-':
			case '_':
			case '.':
			case '!':
			case '~':
			case '*':
			case '\'':
			case '(':
			case ')':
			case '"':
			case '/':
				result += str[i];
				break;
			default:
				ch = (str[i] & MASK1) >> 4;
				result += '%';

				if (ch > 9)
				{
					ch = ch - 10 + 'A';
				}
				else
				{
					ch = ch + '0';
				}
				result += ch;
				ch = str[i] & MASK2;
				if (ch > 9)
				{
					ch = ch - 10 + 'A';
				}
				else
				{
					ch = ch + '0';
				}
				result += ch;
				break;
			}
		}
	}
	return result;
}

size_t getFileSize(const string& subFilePath)
{
	struct stat st;
	size_t size = 0;
	if (stat(subFilePath.c_str(), &st) == 0)
	{
		size = st.st_size;
	}
	return size;
}

string getFileSizeStr(size_t size)
{
	char buf[64];

	char unit_str = 'B';
	if (size >= 1024)
	{
		// in k
		size = size / 1024;
		unit_str = 'K';
		if (size >= 1024)
		{
			// in M
			size = size / 1024;
			unit_str = 'M';
			if (size >= 1024)
			{
				// in G
				size = size / 1024;
				unit_str = 'G';
			}
		}
	}
	sprintf(buf, "[%lu%c]", (unsigned long) size, unit_str);
	//printf("filename=%s has size of %s\n", p.filename().c_str(), buf);
	return string(buf);
}

void doFile(const string& strFileName,
            StringMap& stringMap,
            StringVector& stringVector,
            const string& strUrl,
            const string& strRelativePath,
            const string& strFileSizeStr)
{
	//cout<< "dofile: " << filePath.string()<<endl;
	//cout<<filePath.filename()<<endl;

	if (stringMap.insert(
			make_pair<string, string>(string(strRelativePath + strFileSizeStr),
					string(strUrl))) == stringMap.end())
	{
		//cout<<"error"<<endl;
	}
	stringVector.push_back(make_pair<string, string>(strRelativePath + strFileSizeStr, string(strUrl)));
}

void doDir(const string& strRootPath,
           const string& strUrl,
           const string& strRelativePath,
           StringMap& stringMap,
           StringVector& stringVector,
           DirStruct* pDirStruct)
{
	string strPath = strRootPath + "/" + strRelativePath;
	DIR* pDir = opendir(strPath.c_str());
	if (pDir)
	{
		struct dirent* pEnt;
		while ((pEnt = readdir(pDir))!= NULL)
		{
			string strFileName = pEnt->d_name;
			string strSubPath = strPath + "/" + strFileName;
			string strSubUrl = strUrl + "/" + urlEncode(strFileName);
			string strSubRelativePath = strRelativePath + "/" + strFileName;
			switch (pEnt->d_type)
			{
			case DT_REG:
				// I don't want to add hidden files now...
				if (strFileName[0] != '.')
				{
					size_t size = getFileSize(strSubPath);
					string strFileSizeStr = getFileSizeStr(size);
					FileStruct fileStruct;
					fileStruct.name = strFileName;
					fileStruct.url = strSubUrl;
					fileStruct.size = strFileSizeStr;
					fileStruct.path = strSubRelativePath;
					pDirStruct->files.push_back(fileStruct);
					doFile(strFileName, stringMap, stringVector, strSubUrl, strSubRelativePath, strFileSizeStr);
				}
				break;
			case DT_DIR:
				if (strFileName.compare("personal") != 0
				        && strFileName.compare("repo.git") !=0
				        && strFileName[0] != '.'
				        && strFileName.compare(g_GeneratedTree) != 0
				   )
				{
				    DirStruct* pSubDirStruct = new DirStruct();
				    pSubDirStruct->name = strFileName;
				    pSubDirStruct->url = strSubUrl;
				    pSubDirStruct->path = strSubRelativePath;
				    pDirStruct->dirs.push_back(pSubDirStruct);
					doDir(strRootPath, strSubUrl, strSubRelativePath, stringMap, stringVector, pSubDirStruct);
				}
				break;
			case DT_LNK:
				cout<<"**************************file is symbolic link:"<< strRootPath << "/"<< strRelativePath << "/" << strFileName << "***********************************"<<endl;
				break;
			default:
				cout<<"file is not regular:"<< strRootPath << "/"<< strRelativePath << "/" << strFileName <<endl;
				break;
			}
		}
		closedir(pDir);
	}
}

void showString(const string& str)
{
	for (size_t i = 0; i < str.size(); i++)
	{
		unsigned char ch = str[i];
		cout << ch;
		//cout.flush();
	}
}

void showMap(const StringMap& stringMap)
{
	cout << header1 << header2 << header3 << endl;
	cout << tableHeader1 << tableHeader2 << endl;
	cout << comment1 << g_prefix << comment2 << endl;
	int counter = 1;
	for (StringMap::const_iterator it = stringMap.begin();
			it != stringMap.end(); it++)
	{
		size_t pos = it->first.find_last_of('/');
		cout << tableColumn1 << counter << tableColumn2;
		showString(it->second);
		cout << tableColumn3;
		string str = it->first.substr(pos + 1);
		pos = str.find_last_of('[');
		showString(str.substr(0, pos));
		cout << tableColumn4;

		cout << it->first;
		cout << tableColumn5;
		counter++;
	}
	cout << bottom << endl;
}
void outputString(ofstream& out, const string& str)
{
	for (size_t i = 0; i < str.size(); i ++)
	{
		unsigned char ch = str[i];
		out << ch;
	}
}
#define MAX_FILE_COUNT_PER_PAGE 10000

static string getNextFileName(const string& strFileBaseName, const string& strPosfix, int index)
{
   if (index > 0)
   {
       // only the next file we need add index
       stringstream ss;
       ss << index;
       return strFileBaseName + ss.str() + strPosfix;
       // close previous file at end

   }
   return strFileBaseName + strPosfix;
}

static void closeFile(ofstream& out, const string& strPrevFileName, const string& strNextFileName, int index, bool isLast)
{
    if (index ==0)
    {
        return;
    }
    out << "</table>";
    if (index > 1)
    {
        out << "<a href=\"" << strPrevFileName << "\"> prev </a>";
    }
    if (!isLast)
    {
        out << "<a href=\"" << strNextFileName << "\"> next </a>";
    }
    out << "</body></html>";
    out.close();
}

static string int2string(int index)
{
    stringstream ss;
    ss << index;
    return ss.str();
}

static string nextDirFileName(int index)
{

    return int2string(index) + ".html";
}

static string convertPath2Name(const string& strPath)
{
    string strResult;
    for (size_t i = 0; i < strPath.size(); i ++)
    {
        if (strPath[i]=='/')
        {
            strResult.push_back('_');
        }
        else
        {
            strResult.push_back(strPath[i]);
        }
    }
    strResult += ".html";
    return strResult;
}

static void outputDir(const DirStruct* ptr,
                      const string& strParentUrl,
                      const string& strSrcPath
                      )
{
    string strDirPath = strSrcPath + "/" + g_GeneratedTree + "/" + convertPath2Name(ptr->path);

    ofstream out(strDirPath.c_str());
    if (out.good())
    {
        // initialize headers etc.
        out << header1 << header2 << header3 << endl;
        out << ptr->name << endl;
        out << tableHeader1 << tableHeader2 << endl;
        out << comment1 << g_prefix << comment2 << endl;
        for (size_t i = 0; i < ptr->files.size(); i ++)
        {
            const FileStruct& file = ptr->files[i];
            out << tableColumn1 << i + 1 << tableColumn2;

            outputString(out, file.url);
            out << tableColumn3;
            out << file.name;

            out << tableColumn4;
            string str = file.path;
            str += file.size;
            outputString(out, str);
            out << tableColumn5;
        }
        out << "</table>" << endl;
        string strCurrentUrl = g_prefix + "/" + g_GeneratedTree + "/" + convertPath2Name(ptr->path);
        for (size_t i = 0; i < ptr->dirs.size(); i ++)
        {
            const DirStruct* pSubDir = ptr->dirs[i];
            string strSubDirUrl = g_prefix + "/" + g_GeneratedTree + "/" + convertPath2Name(pSubDir->path);
            out << "<p><a href=\"" << endl;
            outputString(out, strSubDirUrl);
            out << "\">" << endl;
            outputString(out, pSubDir->path);
            out << "</a></p>" <<endl;
        }

        out << "<p><a href=\"" << strParentUrl << "\">parent</a></p>" << endl;

        out << "</body></html>" << endl;
        out.close();

        // now iterate every sub dirs
        for (size_t i = 0; i < ptr->dirs.size(); i ++)
        {
            const DirStruct* pSubDir = ptr->dirs[i];
            outputDir(pSubDir, strCurrentUrl, strSrcPath);
        }
    }
}

void output2File(const StringMap& stringMap,
                 const StringVector& stringVector,
                 const string& strSrcPath,
                 const string& strFileName,
                 const string& strFileVectorName,
                 const DirStruct& dirStruct)
{
//    {
//        string strPath = strSrcPath +"/" + strFileName;
//        ofstream out(strPath.c_str());
//        if (out.good())
//        {
//            out << header1 << header2 << header3 << endl;
//            out << tableHeader1 << tableHeader2 << endl;
//            out << comment1 << prefix << comment2 << endl;
//            int counter = 1;
//            for (StringMap::const_iterator it = stringMap.begin();
//                    it != stringMap.end(); it++)
//            {
//                size_t pos = it->first.find_last_of('/');
//                out << tableColumn1 << counter << tableColumn2;
//
//                outputString(out, it->second);
//                out << tableColumn3;
//                string str = it->first.substr(pos + 1);
//                pos = str.find_last_of('[');
//                outputString(out, str.substr(0, pos));
//                out << tableColumn4;
//
//                out << it->first;
//                out << tableColumn5;
//                counter++;
//            }
//            out << bottom << endl;
//        }
//    }

    {
        ofstream out;
        const DirStruct* ptr = &dirStruct;
        outputDir(ptr, g_prefix, strSrcPath);
    }
    {
        int counter = 0;
        size_t pos = strFileName.find_last_of(".");
        string strFileBaseName = strFileName.substr(0, pos);
        string strPostFix = strFileName.substr(pos);
        ofstream out;
        string strPrevFileName = strFileName;
        string strCurrFileName = strFileName;
        string strNextFileName = strFileName;
        int index = 0;
        for (StringMap::const_iterator it = stringMap.begin();
             it != stringMap.end(); it++)
        {
            if (counter % MAX_FILE_COUNT_PER_PAGE == 0)
            {
                // GET FILE NAME
                index = counter / MAX_FILE_COUNT_PER_PAGE;
                strNextFileName = getNextFileName(strFileBaseName, strPostFix, index);
                // before open new file, close current file
                closeFile(out, strPrevFileName, strNextFileName, index, false);
                // roll over file name
                strPrevFileName = strCurrFileName;
                strCurrFileName = strNextFileName;

                string strPath = strSrcPath +"/" + strCurrFileName;
                out.open(strPath.c_str());
                if (!out.good())
                {
                    cout << "unexpected error open file " << strPath << endl;
                    break;
                }

                // initialize headers etc.
                out << header1 << header2 << header3 << endl;
                out << tableHeader1 << tableHeader2 << endl;
                out << comment1 << g_prefix << comment2 << endl;
            }
            size_t pos = it->first.find_last_of('/');
            out << tableColumn1 << counter + 1 << tableColumn2;

            outputString(out, it->second);
            out << tableColumn3;
            string str = it->first.substr(pos + 1);
            pos = str.find_last_of('[');
            outputString(out, str.substr(0, pos));
            out << tableColumn4;

            out << it->first;
            out << tableColumn5;
            counter++;
        }
        closeFile(out, strPrevFileName, strNextFileName, index, true);
    }
    {
        string strPath = strSrcPath +"/" + strFileVectorName;
        ofstream out(strPath.c_str());
        if (out.good())
        {
            out << header1 << header2 << header3 << endl;
            out << tableHeader1 << tableHeader2 << endl;
            out << comment1 << g_prefix << comment2 << endl;
            int counter = 1;
            for (StringVector::const_iterator it = stringVector.begin();
                    it != stringVector.end(); it++)
            {
                size_t pos = it->first.find_last_of('/');
                out << tableColumn1 << counter << tableColumn2;

                outputString(out, it->second);
                out << tableColumn3;
                string str = it->first.substr(pos + 1);
                pos = str.find_last_of('[');
                outputString(out, str.substr(0, pos));
                out << tableColumn4;

                out << it->first;
                out << tableColumn5;
                counter++;
            }
            out << bottom << endl;
        }
    }
}



void myListTool(const string& srcPath)
{
	string strPath = realpath(srcPath.c_str(), NULL);
	//path dstPath = srcPath.parent_path() / "dst";
	StringMap stringMap;
	StringVector stringVector;
	DirStruct dirStruct;

//	size_t pos = strPath.find_last_of('/');
//	string strFileName = strPath;
//	if (pos != string::npos)
//	{
//	    strFileName = strPath.substr(pos+1);
//	}
//	dirStruct.name = strFileName;
//	dirStruct.path =
    dirStruct.path = "/";
    dirStruct.path += g_GeneratedTree;
    dirStruct.name = g_GeneratedTree;
    dirStruct.url = g_prefix + "/" + g_GeneratedTree;
	doDir(strPath, g_prefix, "", stringMap, stringVector, &dirStruct);

	//for_each(stringMap.begin(), stringMap.end(), ShowStringPair());
	//showMap(stringMap);
	output2File(stringMap, stringVector, strPath, targetFileName, targetFileName2, dirStruct);

}
