/*
 * =====================================================================================
 * 
 *        Filename:  ourscript.cc
 * 
 *     Description:  parses some file, uses the commands to generate a webpage.
 *                   it's cool stuff, don't worry.
 * 
 *         Version:  1.0
 *         Created:  08/19/04 21:53:10 EDT
 *        Revision:  none
 *        Compiler:  g++
 * 
 *          Author:   Natan 'whatah' Zohar 
 * 
 * =====================================================================================
 */

#include <cstdlib>
#include <sstream>
#include <fstream>
#include <iostream>
#include <vector>
#include <ctime>
#include <unistd.h>
#include <getopt.h>

#define VERSION "0.0.2"
using namespace std;

// global declarations
// defaults, if not set before
string SS_COMMAND="import -w root", ARCHIVE_DIR="archived/", SS_PREFIX="ss-",\
                   FILE_FORMAT="png", TITLE="My Screenshot", CSS_FILE="", \
                   TEMPLATE_FILE="", SPECS_DIR="", SS_FILE="";


// convert to a string from class T (used by getdate)
template<class T>
string toStr(T val) {
    stringstream ss;
    ss << val;
    return ss.str();
}

// string tokenizer
vector<string> tokenize(string toTokens, char delim) {
    vector<string> tokenArray;
    char c;
    string tmpWord;
    for (int i = 0; i < (signed) toTokens.length(); i++) {
        c = toTokens.at(i);
        if (c == delim) {
            (tokenArray).push_back(tmpWord);
            tmpWord = "";
        } else 
            tmpWord.push_back(c);

    }
    if (tmpWord != "")
        (tokenArray).push_back(tmpWord);
    return tokenArray;
}

// file writing function, either writes to file or stdout
int write_to_file(string some_file, string message, bool to_stdout) {
    if (!to_stdout) {
        ofstream outfile_writer(some_file.c_str(), ios::app);
        outfile_writer << message << endl;
        outfile_writer.close();
    } else {
        cout << message << endl;
    }
    return 0;
}

// writes to stderr, so if there is an error, you don't miss it on a redirect
int write_to_stderr(string message) {
    write(2, message.c_str(), message.length());
    return 0;
}

// remove blanks from strings (used by the copyfile)
string remove_blanks(string string_with_blanks) {
    string return_string = "";
    for (int i = 0; i < (signed) string_with_blanks.length(); i++) {
        if (string_with_blanks[i] != ' ')
            return_string += string_with_blanks[i];
    }
    return return_string;
}

// execute a command into an output file and return a link to the file
string handle_command(string command_string) {
    // do stuff here, it involves tokenizing.
    //cout << "command" << endl;
    vector<string> tokenized = tokenize(command_string, ',');
    if (tokenized.size() < 3) {
        write_to_stderr("ERROR: Not enough arguments in: " + command_string);
        exit(1);
    }
    string outfile = tokenized[0] + ".out";
    string args = tokenized[1];
    string command = tokenized[0] + " " + args + " > " + SPECS_DIR + outfile;
    string text = tokenized[2];
    if (system(command.c_str()) == -1) { // we need some error handling, possibly
        write_to_stderr(command + "did not execute");
    }
    return "<a href=\"" + SPECS_DIR + outfile + "\">" + text + "</a>";
}

// makes the text bolden
string handle_bolden(string bolden_string) {
    return "<b>" + bolden_string + "</b>";
}

// makes the text a header
string handle_header(string header_string) {
    return "<h2>" + header_string + "</h2>";
}

// takes a url and makes a link out of it
string handle_url(string url_string) {
    vector<string> tokenized = tokenize(url_string, ',');
    string url = tokenized[0];
    string text = tokenized[1];
    return "<a href=\"" + url + "\">" + text + "</a>";
}

// executes a command and returns the text from it
string handle_command_output(string comoutput_string) {
    char c;
    string output = "";
    FILE *stdout_file = popen(comoutput_string.c_str(), "r");
    while (fread(&c, 1, 1, stdout_file) > 0) {
        output += c;
    }
    pclose(stdout_file);
    return output + " ";
}

// copies a file to a localfile and makes a link to it.
string handle_copyfile(string copyfile_string) {
    vector<string> tokenized = tokenize(copyfile_string, ',');
    if (tokenized.size() < 3) {
        write_to_stderr("ERROR: missing arguments");
        exit(1);
    }
    string text = tokenized[2];
    ifstream old_file(remove_blanks(tokenized[0]).c_str());
    ofstream new_file((SPECS_DIR + remove_blanks(tokenized[1])).c_str());
    string new_file_link = remove_blanks(tokenized[1]);
    string line;
    while (getline(old_file, line)) {
        new_file << line << endl;
    }
    old_file.close();
    new_file.close();
    return "<a href=\"" + SPECS_DIR + new_file_link + "\">" + text + "</a>";
}

string get_css() {
    ifstream css_file(CSS_FILE.c_str());
    string lines;
    string return_string = "<style type=\"text/css\">\n<!--";
    while(getline(css_file, lines)) {
        return_string += lines + "\n";
    }
    return_string += "\n// -->\n</style>\n";
    return return_string;
}

// get the current date (useful for taking the screenshot)
string get_date() {
    time_t cur_time = time(NULL);
    struct tm *s_date = localtime(&cur_time);
    string date = s_date->tm_mon+1>=10?"":"0";
    date += toStr<int>(s_date->tm_mon+1);
    date += s_date->tm_mday>=10?"":"0";
    date += toStr<int>(s_date->tm_mday);
    date += s_date->tm_year%100>=10?"":"0"; 
    date += toStr<int>(s_date->tm_year%100);
    return date;
}

// return the used template (if we used one)
string get_template() {
    if (TEMPLATE_FILE != "") {
        return "using " + handle_copyfile(TEMPLATE_FILE + ",template.out,this template");
    } else {
        return "";
    }

} 

// way to take a screenshot 
int take_screenshot() {
    // Using global vars
    string dir = " " + ARCHIVE_DIR; 
    string command = SS_COMMAND.c_str();
    string file = dir;
    // check to see if a specific file name is set for the screenshot
    if (SS_FILE == "") {
        file  += SS_PREFIX + get_date() + "." + FILE_FORMAT;
    } else {
        file += SS_FILE;
    } 
    string linkage = "ln -s " + file + " screenshot.png";
    string unlinkage = "unlink screenshot.png";
    system(("mkdir -p " + dir).c_str());
    system((command + file).c_str());
    system(unlinkage.c_str());
    system(linkage.c_str());
    return 0;
}


// beginning html code
string html_init() {

    return "<!DOCTYPE html \
        \nPUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \
        \n\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"> \
        \n<html>\n<head>\n" + get_css() + "<title>"+TITLE+"</title>\n</head> \
        \n<body>\n<p>\n<a href=\"" + ARCHIVE_DIR + "\"> \
        \n<img src=\"screenshot.png\" name=\"Screenshot\" alt=\"Screenshot\" />\n</a>\n</p>\n";
}

// ending html code
string html_endit() {
    return "<br />Generated by <a href=\"http://bingweb.binghamton.edu/~nzohar1/code/whatahshot\">whatah-shot</a>\n" + get_template() + "</body>\n</html>";
}

// read options from the file

// recursively parse the file we opened, calling handle_xxxx commands as needed
string rec_parse_cmd(string some_string) {
    vector<string> tokenArray;
    //cout << "recursion!" << endl;
    string returnstring;
    char c;
    string tmpWord;
    for (int i = 0; i < (signed) some_string.length(); i++) {
        c = some_string.at(i);
        if (c == ' ') {
            if (tmpWord != " ") {
                tokenArray.push_back(tmpWord);
            }
            tmpWord = "";
        } else if (c == '(') {
            tokenArray.push_back(tmpWord);
            tmpWord = "";
            int f = some_string.find_first_of(")", i);
            if (tokenArray.back() == "COMMAND") {
                tokenArray.erase(tokenArray.end());
                tokenArray.push_back(handle_command(rec_parse_cmd(some_string.substr(i+1, f-1))));
            } else if (tokenArray.back() == "BOLD") {
                tokenArray.erase(tokenArray.end());
                tokenArray.push_back(handle_bolden(rec_parse_cmd(some_string.substr(i+1, f-1))));
            } else if (tokenArray.back() == "HEADER") {
                tokenArray.erase(tokenArray.end());
                tokenArray.push_back(handle_header(rec_parse_cmd(some_string.substr(i+1, f-1))));
            } else if (tokenArray.back() == "URL") {
                tokenArray.erase(tokenArray.end());
                tokenArray.push_back(handle_url(rec_parse_cmd(some_string.substr(i+1, f-1))));
            } else if (tokenArray.back() == "COUTPUT") {
                tokenArray.erase(tokenArray.end()); 
                tokenArray.push_back(handle_command_output(rec_parse_cmd(some_string.substr(i+1, f-1)))); 
            } else if (tokenArray.back() == "COPYFILE") {
                tokenArray.erase(tokenArray.end());
                tokenArray.push_back(handle_copyfile(rec_parse_cmd(some_string.substr(i+1, f-1))));
            } else {
                // error out
            }
            i = f;
        } else if (c == ')') {
            string another_string = "";
            tokenArray.push_back(tmpWord);
            for (int j = 0; j < (signed) tokenArray.size(); j++) {
                another_string += tokenArray[j] + " ";
            }
            return another_string;
        } else if (c == '#') {
            tokenArray.push_back(tmpWord);
            break;
        } else {
            tmpWord += c;
        }

    }
    if (tmpWord != "" && c != '#')
        (tokenArray).push_back(tmpWord);
    for (int i = 0; i < (signed) tokenArray.size(); i++)
        returnstring += tokenArray[i] + " ";
    return tokenArray.size() > 0?returnstring:"";
}

string parse_command(string some_string) {
    return "<p>" + rec_parse_cmd(some_string) + "</p>";
}

void parse_option(string some_string) {
    // options are in format [option] {value} 
    string option;
    string value;
    string tmpWord;
    char c;
    int end_option;
    for (int i = 1; i < (signed) some_string.size(); i++) {
        c = some_string[i];
        if (c != ']')  
            tmpWord += (c);
        else {
            option = tmpWord;
            end_option = i;
            break;
        }
    }
    if (option == "") {
        // we need to make an error function.
    }
    tmpWord = "";
    bool started = false;
    for (int i = end_option; i < (signed) some_string.size(); i++) {
        c = some_string[i];
        if (c == '}') {
            started = false;
        }
        if (started) {
            tmpWord += c;
        }
        if (c == '{') {
            started = true;
        } 

    }
    value = tmpWord;

    // check if the option is real. 
    if (option == "SS_COMMAND") {
        SS_COMMAND = value;
    } else if (option == "ARCHIVE_DIR") {
        ARCHIVE_DIR = value;
    } else if (option == "SS_PREFIX") {
        SS_PREFIX = value;
    } else if (option == "FILE_FORMAT") {
        FILE_FORMAT = value;
    } else if (option == "TITLE") {
        TITLE = value;
    } else if (option == "SPECS_DIR") {
        SPECS_DIR = value;
        system(("mkdir -p " + SPECS_DIR).c_str());
    } else if (option == "CSS_FILE") {
        CSS_FILE = value;
    } else {
        write_to_stderr("no such option: " + option);
    }
}

// display help to stderr
string display_help() {
    return "whatahshot " + string(VERSION) + "\nUsage: whatahshot [OPTION] ... \n\n \
        -t, --template[=FILE]   use template file to generate output \n \
        -c, --config=[FILE]     use file as config, instead of $HOME/.whatahrc\n \
        -s, --stylesheet[=FILE] specifies stylesheet to use\n \
        -o, --output[=FILE]     output file \n \
        -u, --update-screenshot take new screenshot, no html\n \
        -g, --take-ss           take a new screenshot\n \
        -n, --ss-name[=NAME]    store screenshot as NAME\n \
        -v, --version           display version\n \
        -h, --help              display this message\n\n";
}


// an idea is to take a screenshot, save it to some archived file and make a symbolic link to it
int main(int argc, char **argv) {
    bool html_only = true, screenshot_only = true, to_stdout = true, read_options = true;
    string input_file, options_file, output_file, lines;
    ifstream options_infile;
    int c;

    // START OPTION PARSING
    while (true) {
        int option_index = 0;
        static struct option long_options[] = {
            {"template", 1, 0, 't'},
            /*{"options-file", 1, 0, 'c'},*/
            {"stylesheet", 1, 0, 's'},
            {"outfile", 1, 0, 'o'},
            {"take-ss", 0, 0, 'g'},
            {"ss-name", 1, 0, 'n'},
            {"help", 0, 0, 'h'},
            {"update-screenshot", 0, 0, 'u'},
            {"version", 0, 0, 'v'},
            {0, 0, 0, 0}
        };

        c = getopt_long (argc, argv, "t:o:c:s:gn:huv012",
                long_options, &option_index);
        if (c == -1)
            break;

        switch (c) {
            case 0:
                cout << long_options[option_index].name << endl;
                if (optarg)
                    cout << optarg << endl;
                break;
            case 't':
                input_file = optarg;
                TEMPLATE_FILE = input_file;
                screenshot_only = false;
                break;

            case 'c':
                options_file = optarg;
                options_infile.open(options_file.c_str());
                while (getline(options_infile, lines)) {
                    if (lines[0] == '[') {
                        parse_option(lines);
                    }
                }
                options_infile.close();
                read_options = false;
                break;
            case 's':
                CSS_FILE = optarg;
                screenshot_only = false;
                break;

            case 'o':
                output_file = optarg;
                to_stdout = false;
                screenshot_only = false;
                break;
            case 'n':
                SS_FILE = optarg;
                html_only = false;
                break;
            case 'g': 
                html_only = false;
                screenshot_only = false;
                break;
            case 'u':
                screenshot_only = true;
                html_only = false;
                break;

            case 'h':
                write_to_stderr(display_help());
                return 0;

            case 'v':
                write_to_stderr(string(VERSION));
                return 0;

            default:
                return 1;
        }
    }
    // END OPTION PARSING

    //   cout << "input file: " << input_file << endl;
    //   cout << "output_file: " << output_file << endl;
    //   cout << "screenshot? " << screenshot_only << endl;
    //   cout << "html_only? " << html_only << endl;

    // overwrite outfile
    ofstream flush_out(output_file.c_str(), ios::trunc);
    flush_out.close();

    // read options from option file
    if (read_options) {
        string home_dir = getenv("HOME") == NULL?"":getenv("HOME");
        if (home_dir != "") {
            options_infile.open(string(home_dir + "/.whatahrc").c_str());
            while (getline(options_infile, lines)) {
                if (lines[0] == '[') {
                    parse_option(lines);
                }
            }
            options_infile.close();
        }else {
            write_to_stderr("Error reading from $HOME");
        }
    }

    ifstream parse_infile(TEMPLATE_FILE.c_str());
    // read file line by line and generate html, possibly
    if (screenshot_only == false) {
        write_to_file(output_file, html_init(), to_stdout);
        while (getline(parse_infile, lines)) {
            if (lines[0] != '#' && lines.length() > 0 && lines[0] != '[') {
                write_to_file(output_file, parse_command(lines), to_stdout);
            } 
        }
        write_to_file(output_file, html_endit(), to_stdout) ;
    }
    parse_infile.close();
    // take a screenshot
    if (html_only == false) {
        take_screenshot();
    }
    // if html and screenshots are unset, we show help
    if (screenshot_only && html_only) {
        write_to_file(output_file, display_help(), to_stdout);
    }
}

