//
//  Copyright (C) 2016  Claire Xenia Wolf <claire@yosyshq.com>
//  Copyright (C) 2019  Sylvain Munaut <tnt@246tNt.com>
//
//  Permission to use, copy, modify, and/or distribute this software for any
//  purpose with or without fee is hereby granted, provided that the above
//  copyright notice and this permission notice appear in all copies.
//
//  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
//  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
//  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
//  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
//  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
//  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
//  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <stdint.h>
#ifdef _WIN32
#define NOMINMAX
#include "windows.h"
#undef NOMINMAX
#else
#include <unistd.h>
#include <sys/time.h>
#endif

#include <map>
#include <vector>
#include <string>
#include <fstream>
#include <iostream>

#include <boost/program_options.hpp>

#include "ChipConfig.hpp"
#include "Chip.hpp"
#include "Database.hpp"
#include "DatabasePath.hpp"
#include "wasmexcept.hpp"

using std::map;
using std::pair;
using std::vector;
using std::string;
using std::ifstream;
using std::getline;

uint64_t x;
uint64_t xorshift64star(void) {
    x ^= x >> 12; // a
    x ^= x << 25; // b
    x ^= x >> 27; // c
    return x * UINT64_C(2685821657736338717);
}

void push_back_bitvector(vector<vector<bool>> &hexfile, const vector<int> &digits)
{
    if (digits.empty())
        return;

    hexfile.push_back(vector<bool>(digits.size() * 4));

    for (int i = 0; i < int(digits.size()) * 4; i++)
        if ((digits.at(digits.size() - i/4 -1) & (1 << (i%4))) != 0)
            hexfile.back().at(i) = true;
}

void parse_hexfile_line(const char *filename, int linenr, vector<vector<bool>> &hexfile, string &line, int &address)
{
    vector<int> digits;
    bool reading_address = false;
    
    for (char c : line) {
        if ('0' <= c && c <= '9')
            digits.push_back(c - '0');
        else if ('a' <= c && c <= 'f')
            digits.push_back(10 + c - 'a');
        else if ('A' <= c && c <= 'F')
            digits.push_back(10 + c - 'A');
        else if ('x' == c || 'X' == c ||
             'z' == c || 'Z' == c)
            digits.push_back(0);
        else if ('_' == c)
            ;
	else if ('@' == c) {
	    if (reading_address || !digits.empty())
		goto error;
	    else
		reading_address = true;
        } else if (' ' == c || '\t' == c || '\r' == c) {
	    if (reading_address) {
		int file_address = 0;
		for (int i = 0; i < int(digits.size()); i++ ) {
		    file_address <<= 4;
		    file_address |= digits.at(i);
		}
		if (file_address != address) {
		    fprintf(stderr, "Non-contiguous address (expected @%X) at line %d of %s: %s\n", address, linenr, filename, line.c_str());
		    exit(1);
		}
	    } else {
		push_back_bitvector(hexfile, digits);
		if( !digits.empty() )
		    address++;
	    }
	    digits.clear();
	    reading_address = false;
        } else goto error;
    }

    push_back_bitvector(hexfile, digits);

    return;

error:
    fprintf(stderr, "Can't parse line %d of %s: %s\n", linenr, filename, line.c_str());
    exit(1);
}

int main(int argc, char **argv)
{
    bool verbose = false;
    namespace po = boost::program_options;

    std::string database_folder = get_database_path();;

    po::options_description options("Allowed options");

    po::options_description options_any("Generic options");
    options_any.add_options()("help,h", "show help");
    options_any.add_options()("verbose,v", "verbose output");

    po::options_description options_init("Initialize options");
    options_init.add_options()("input,i", po::value<std::string>(), "input configuration file");
    options_init.add_options()("output,o", po::value<std::string>(), "output configuration file");
    options_init.add_options()("from,f", po::value<std::string>(), "original content hex file");
    options_init.add_options()("to,t", po::value<std::string>(), "new content hex file");

    po::options_description options_gen("Generate options");
    options_gen.add_options()("generate,g", po::value<std::string>(), "Generate random hex of given geometry into given file");
    options_gen.add_options()("seed,s", po::value<int>(), "seed random generator with fixed value");
    options_gen.add_options()("width,w", po::value<int>(), "width of the BRAM content (in bits)");
    options_gen.add_options()("depth,d", po::value<int>(), "idepth of the BRAM content (in # of words)");

    options.add(options_any).add(options_init).add(options_gen);

    po::variables_map vm;
    try {
        po::parsed_options parsed = po::command_line_parser(argc, argv).options(options).run();
        po::store(parsed, vm);
        po::notify(vm);
    }
    catch (std::exception &e) {
        cerr << e.what() << endl << endl;
        goto help;
    }

    if (vm.count("help")) {
    help:
        cerr << "Project Trellis - Open Source Tools for ECP5 FPGAs" << endl;
        cerr << argv[0] << ": ECP5 BRAM content initialization tool" << endl;
        cerr << endl;
        cerr << "Copyright (C) 2019  Sylvain Munaut <tnt@246tNt.com>" << endl;
        cerr << "Copyright (C) 2016  Claire Xenia Wolf <claire@yosyshq.com>" << endl;
        cerr << endl;
        cerr << options << endl;
        return vm.count("help") ? 0 : 1;
    }

    if (vm.count("verbose"))
        verbose = true;

    if (vm.count("generate"))
    {
        if (!vm.count("width") || !vm.count("depth"))
            goto help;

        int width = vm.at("width").as<int>();
        int depth = vm.at("depth").as<int>();

        if (width <= 0 || width % 4 != 0) {
            fprintf(stderr, "Hexfile width (%d bits) is not divisible by 4 or nonpositive!\n", width);
            return 1;
        }

        if (depth <= 0 || depth % 512 != 0) {
            fprintf(stderr, "Hexfile number of words (%d) is not divisible by 512 or nonpositive!\n", depth);
            return 1;
        }

        // If -s is provided: seed with the given value.
        // If -s is not provided: seed with the PID and current time, which are unlikely 
        // to repeat simultaneously.
        uint32_t seed_nr;
        if (vm.count("seed")) {
            seed_nr = vm.at("seed").as<int>();

            if (verbose)
                fprintf(stderr, "Seed: %d\n", seed_nr);
        } else {
#if defined(__wasm)
            seed_nr = 0;
#elif defined(_WIN32)
            seed_nr = GetCurrentProcessId();
#else
            seed_nr = getpid();
#endif
        }

        x  = uint64_t(seed_nr) << 32;
        x ^= uint64_t(seed_nr) << 20;
        x ^= uint64_t(seed_nr);

        x ^= uint64_t(depth) << 16;
        x ^= uint64_t(width) << 10;

        xorshift64star();
        xorshift64star();
        xorshift64star();

        if (!vm.count("seed")) {
#ifdef _WIN32
            SYSTEMTIME system_time;
            FILETIME file_time;
            uint64_t time;
            GetSystemTime(&system_time);
            SystemTimeToFileTime(&system_time, &file_time);
            x ^= uint64_t(file_time.dwLowDateTime);
            x ^= uint64_t(file_time.dwHighDateTime) << 32;
#else
            struct timeval tv;
            gettimeofday(&tv, NULL);
            x ^= uint64_t(tv.tv_sec) << 20;
            x ^= uint64_t(tv.tv_usec);
#endif
        }

        xorshift64star();
        xorshift64star();
        xorshift64star();

        ofstream romfile(vm.at("generate").as<std::string>());

        for (int i = 0; i < depth; i++) {
            for (int j = 0; j < width / 4; j++) {
                int digit = xorshift64star() & 15;
                romfile << "0123456789abcdef"[digit];
            }
            romfile << std::endl;
        }

        return 0;
    }

    if (!vm.count("input") || !vm.count("output") || !vm.count("from") || !vm.count("to"))
        goto help;

    // -------------------------------------------------------
    // Load from_hexfile and to_hexfile

    const char *from_hexfile_n = vm.at("from").as<std::string>().c_str();
    ifstream from_hexfile_f(from_hexfile_n);
    vector<vector<bool>> from_hexfile;

    const char *to_hexfile_n = vm.at("to").as<std::string>().c_str();
    ifstream to_hexfile_f(to_hexfile_n);
    vector<vector<bool>> to_hexfile;

    string line;
    
    for (int i = 1, address = 0; getline(from_hexfile_f, line); i++)
        parse_hexfile_line(from_hexfile_n, i, from_hexfile, line, address);

    for (int i = 1, address = 0; getline(to_hexfile_f, line); i++)
        parse_hexfile_line(to_hexfile_n, i, to_hexfile, line, address);

    if (to_hexfile.size() > 0 && from_hexfile.size() > to_hexfile.size()) {
        if (verbose)
            fprintf(stderr, "Padding to_hexfile from %d words to %d\n",
                int(to_hexfile.size()), int(from_hexfile.size()));
        do
            to_hexfile.push_back(vector<bool>(to_hexfile.at(0).size()));
        while (from_hexfile.size() > to_hexfile.size());
    }

    if (from_hexfile.size() != to_hexfile.size()) {
        fprintf(stderr, "Hexfiles have different number of words! (%d vs. %d)\n", int(from_hexfile.size()), int(to_hexfile.size()));
        return 1;
    }

    if (from_hexfile.size() % 512 != 0) {
        fprintf(stderr, "Hexfile number of words (%d) is not divisible by 512!\n", int(from_hexfile.size()));
        return 1;
    }

    for (size_t i = 1; i < from_hexfile.size(); i++)
        if (from_hexfile.at(i-1).size() != from_hexfile.at(i).size()) {
            fprintf(stderr, "Inconsistent word width at line %d of %s!\n", int(i), from_hexfile_n);
            return 1;
        }

    for (size_t i = 1; i < to_hexfile.size(); i++) {
        while (to_hexfile.at(i-1).size() > to_hexfile.at(i).size())
            to_hexfile.at(i).push_back(false);
        if (to_hexfile.at(i-1).size() != to_hexfile.at(i).size()) {
            fprintf(stderr, "Inconsistent word width at line %d of %s!\n", int(i+1), to_hexfile_n);
            return 1;
        }
    }

    if (from_hexfile.size() == 0 || from_hexfile.at(0).size() == 0) {
        fprintf(stderr, "Empty from/to hexfiles!\n");
        return 1;
    }

    if (verbose)
        fprintf(stderr, "Loaded pattern for %d bits wide and %d words deep memory.\n", int(from_hexfile.at(0).size()), int(from_hexfile.size()));


    // -------------------------------------------------------
    // Create bitslices from pattern data

    map<vector<bool>, pair<vector<bool>, int>> pattern;

    for (int i = 0; i < int(from_hexfile.at(0).size()); i++)
    {
        vector<bool> pattern_from, pattern_to;

        for (int j = 0; j < int(from_hexfile.size()); j++)
        {
            pattern_from.push_back(from_hexfile.at(j).at(i));
            pattern_to.push_back(to_hexfile.at(j).at(i));

            if (pattern_from.size() == 512) {
                if (pattern.count(pattern_from)) {
                    fprintf(stderr, "Conflicting from pattern for bit slice from_hexfile[%d:%d][%d]!\n", j, j-255, i);
                    return 1;
                }
                pattern[pattern_from] = std::make_pair(pattern_to, 0);
                pattern_from.clear(), pattern_to.clear();
            }
        }

        assert(pattern_from.empty());
        assert(pattern_to.empty());
    }

    if (verbose)
        fprintf(stderr, "Extracted %d bit slices from from/to hexfile data.\n", int(pattern.size()));


    // -------------------------------------------------------
    // Load database and config

    ifstream config_file(vm.at("input").as<string>());

    try {
        Trellis::load_database(database_folder);
    } catch (runtime_error &e) {
        cerr << "Failed to load Trellis database: " << e.what() << endl;
        return 1;
    }

    string textcfg((std::istreambuf_iterator<char>(config_file)), std::istreambuf_iterator<char>());

    Trellis::ChipConfig cc;
    try {
        cc = Trellis::ChipConfig::from_string(textcfg);
    } catch (runtime_error &e) {
        cerr << "Failed to process input config: " << e.what() << endl;
        return 1;
    }

    // -------------------------------------------------------
    // Replace bram data

    int max_replace_cnt = 0;

    for (auto &bram_it : cc.bram_data)
    {
        auto &bram_data = bram_it.second;

        const int W[] =  {  1,  2,  4,  9, 18, 36 };
        const int NW[] = {  8,  8,  8,  9,  9,  9 };
        const int B[]  = { 32, 32, 32, 36, 36, 36 };

        for (int i = 0; i < 6; i++)
        {
            for (int j = 0; j < B[i]; j++)
            {
                vector<bool> from_bitslice;

                for (int k = 0; k < 512; k++)
                {
                    int bn = (k * W[i]) + (j % W[i]) + ((j/W[i]) * 512 * W[i]);
                    int word  = bn / NW[i];
                    int bit   = bn % NW[i];

                    from_bitslice.push_back((bram_data.at(word) & (1 << bit)) != 0);
                }

                auto p = pattern.find(from_bitslice);
                if (p != pattern.end())
                {
                    auto &to_bitslice = p->second.first;

                    for (int k = 0; k < 512; k++)
                    {
                        int bn = (k * W[i]) + (j % W[i]) + ((j/W[i]) * 512 * W[i]);
                        int word  = bn / NW[i];
                        int bit   = bn % NW[i];

                        if (to_bitslice.at(k))
                            bram_data.at(word) |=  (1 << bit);
                        else
                            bram_data.at(word) &= ~(1 << bit);
                    }

                    max_replace_cnt = std::max(++p->second.second, max_replace_cnt);
                }
            }
        }
    }

    // -------------------------------------------------------
    // Save the new config

    ofstream new_config_file(vm.at("output").as<std::string>());
    new_config_file << cc.to_string();

    return 0;
}
