// (c) 2008 Sebastian Redl
// Released under the GPLv2.

#define BOOST_DISABLE_THREADS

#include <ostream>
#include <iostream>
#include <string>
#include <algorithm>
#include <utility>
#include <tr1/unordered_map>
#include <boost/program_options.hpp>
#include <boost/interprocess/file_mapping.hpp>
#include <boost/interprocess/mapped_region.hpp>
#include <boost/filesystem.hpp>

namespace po = boost::program_options;
namespace ip = boost::interprocess;
namespace fs = boost::filesystem;

// The possible modes of sorting footnotes.
enum sort_mode
{
        sort_by_text,
        sort_by_footnote
};

// Input operator for the enum.
std::istream &operator >>(std::istream &is, sort_mode &sm)
{
        std::string s;
        is >> s;
        if(s == "text") {
                sm = sort_by_text;
        } else if(s == "footnote") {
                sm = sort_by_footnote;
        } else {
                is.setstate(std::ios_base::failbit);
        }
        return is;
}

// Output operator for the enum.
std::ostream &operator <<(std::ostream &os, sort_mode sm)
{
        return os << (sm == sort_by_text ? "text" : "footnote");
}

bool parse_command_line(int argc, char **argv,
        sort_mode &sm, std::string &input_file);

std::tr1::unordered_map< int, int>
        g_footnote_number_translations(10000);
int g_footnote_count = 0;

inline int lookup_footnote_translation(int number)
{
        int &r = g_footnote_number_translations[number];
        if(r == 0) {
                r = ++g_footnote_count;
        }
        return r;
}

inline bool isdig(char c)
{
        return c >= '0' && c <= '9';
}

void load_footnote_order(const char *first, const char *last);
void copy_text(const char *first, const char *last, std::ostream &os);
void sort_footnotes(const char *first, const char *last, std::ostream &os);

int main(int argc, char **argv)
{
        std::ios::sync_with_stdio(false);

        sort_mode sm = sort_by_text;
        std::string input_file;

        if(!parse_command_line(argc, argv, sm, input_file)) {
                return 1;
        }

        // Memory-map the input file.
        ip::file_mapping input_map(input_file.c_str(), ip::read_only);
        std::size_t input_size = fs::file_size(fs::path(input_file));
        ip::mapped_region mapping(input_map, ip::read_only, 0, input_size);

        const char *text_start = static_cast<const char*>(mapping.get_address());
        const char *footnote_end = text_start + input_size;

        // Locate the footnote marker. Search in reverse, since it's probably close
        // to the end.
        const char * const MARKER_START = "\n@footnote:\n";
        const char * const MARKER_END = MARKER_START + 12;

        const char* loc = std::find_end(text_start, footnote_end,
                MARKER_START, MARKER_END);
        if(loc == footnote_end) {
                throw std::runtime_error("No footnote section.");
        }

        // Include the last newline.
        const char *text_end = loc + 1;
        const char *footnote_start = loc + 12;

        if(sm == sort_by_footnote) {
                load_footnote_order(footnote_start, footnote_end);
        }

        copy_text(text_start, text_end, std::cout);
        std::cout << "@footnote:\n";
        sort_footnotes(footnote_start, footnote_end, std::cout);
}

bool parse_command_line(int argc, char **argv,
        sort_mode &sm, std::string &input_file)
{
        po::options_description visible_desc("Allowed options");
        visible_desc.add_options()
                ("help,h", "produce help message")
                ("sort-by,s", po::value<sort_mode>(&sm)->default_value(sort_by_text),
                        "sort by order of in-text appearance ('text') or in-footnotes "
                        "appearance ('footnote')")
                ;

        po::options_description hidden_desc("positional options");
        hidden_desc.add_options()("input", po::value<std::string>(&input_file),
                "input file (must be real file)");

        po::positional_options_description positional;
        positional.add("input", 1);

        po::options_description merged("all options");
        merged.add(visible_desc).add(hidden_desc);

        po::variables_map vm;
        try {
                po::store(
                        po::command_line_parser(argc, argv)
                                .options(merged).positional(positional).run(),
                        vm);
                po::notify(vm);
        } catch(po::error &e) {
                std::cerr
                        << "Command line error: " << e.what() << "\n\n"
                        << "Usage: babylon [<options>] <input-file>\n"
                        << visible_desc
                        << '\n';
                return false;
        }

        if(vm.count("help")) {
                std::cerr
                        << "Babylon Challenge in C++, Sebastian Redl "
                           "<sebastian.redl@getdesigned.at>\n"
                        << "Usage: babylon [<options>] <input-file>\n"
                        << visible_desc
                        << '\n';
                return false;
        }
        return true;
}

void load_footnote_order(const char *first, const char *last)
{
        for( ; first != last; ++first) {
                if(*first == '[') {
                        int num = 0;
                        for(++first; first != last && isdig(*first); ++first) {
                                num *= 10;
                                num += *first - '0';
                        }
                        if(first == last) {
                                break;
                        }
                        if(*first == ']' && num != 0) {
                                lookup_footnote_translation(num);
                        }
                }
        }
}

void copy_text(const char *first, const char *last, std::ostream &os)
{
        const char *last_end = first;
        for( ; first != last; ++first) {
                if(*first == '[') {
                        int num = 0;
                        const char *f = first + 1;
                        for( ; f != last && isdig(*f); ++f) {
                                num *= 10;
                                num += *f - '0';
                        }
                        if(f == last) {
                                break;
                        }
                        if(*f == ']' && num != 0) {
                                os.write(last_end, first - last_end + 1);
                                os << lookup_footnote_translation(num);
                                last_end = first = f;
                        }
                }
        }
        os.write(last_end, last - last_end);
}

void sort_footnotes(const char *first, const char *last, std::ostream &os)
{
        typedef std::pair<const char*, const char*> txtrange;
        typedef std::vector<txtrange> txtmap;
        txtmap order(g_footnote_count, txtrange(0, 0));

        for( ; first != last ; ++first) {
                if(*first == '[') {
                        int num = 0;
                        ++first;
                        for( ; first != last && isdig(*first); ++first) {
                                num *= 10;
                                num += *first - '0';
                        }
                        if(first == last) {
                                break;
                        }
                        const char *fnbegin = first;
                        for( ; first != last && *first != '\n'; ++first) {
                        }
                        int idx = lookup_footnote_translation(num);
                        order[idx-1] = txtrange(fnbegin, first);
                        if(first == last) {
                                std::cerr << "Warning: File doesn't end with a newline.\n";
                                break;
                        }
                } else {
                        //std::cerr << "Warning: Bad '" << *first << "' in footnotes.\n";
                }
        }

        for(std::size_t i = 0, len = order.size(); i < len; ++i) {
                const txtrange &r = order[i];
                if(r.first == 0) {
                        os << '[' << (i+1) << "] Error: No footnote text found.\n";
                        std::cerr << "Warning: No footnote for reference " << (i+1) << '\n';
                        continue;
                }
                os << '[' << (i+1);
                os.write(r.first, r.second - r.first);
                os << '\n';
        }
}
