/*
 *  nextpnr -- Next Generation Place and Route
 *
 *  Copyright (C) 2018  gatecat <gatecat@ds0.me>
 *  Copyright (C) 2021  William D. Jones <wjones@wdj-consulting.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 "config.h"
#include <boost/range/adaptor/reversed.hpp>
#include <iomanip>
#include <set>
#include "log.h"

NEXTPNR_NAMESPACE_BEGIN

#define fmt(x) (static_cast<const std::ostringstream &>(std::ostringstream() << x).str())

inline std::string to_string(const std::vector<bool> &bv)
{
    std::ostringstream os;
    for (auto bit : boost::adaptors::reverse(bv))
        os << (bit ? '1' : '0');
    return os.str();
}

inline std::istream &operator>>(std::istream &in, std::vector<bool> &bv)
{
    bv.clear();
    std::string s;
    in >> s;
    for (auto c : boost::adaptors::reverse(s)) {
        assert((c == '0') || (c == '1'));
        bv.push_back((c == '1'));
    }
    return in;
}

struct ConfigBit
{
    int frame;
    int bit;
    bool inv;
};

static ConfigBit cbit_from_str(const std::string &s)
{
    size_t idx = 0;
    ConfigBit b;
    if (s[idx] == '!') {
        b.inv = true;
        ++idx;
    } else {
        b.inv = false;
    }
    NPNR_ASSERT(s[idx] == 'F');
    ++idx;
    size_t b_pos = s.find('B');
    NPNR_ASSERT(b_pos != std::string::npos);
    b.frame = stoi(s.substr(idx, b_pos - idx));
    b.bit = stoi(s.substr(b_pos + 1));
    return b;
}

inline std::string to_string(ConfigBit b)
{
    std::ostringstream ss;
    if (b.inv)
        ss << "!";
    ss << "F" << b.frame;
    ss << "B" << b.bit;
    return ss.str();
}

// Skip whitespace, optionally including newlines
inline void skip_blank(std::istream &in, bool nl = false)
{
    int c = in.peek();
    while (in && (((c == ' ') || (c == '\t')) || (nl && ((c == '\n') || (c == '\r'))))) {
        in.get();
        c = in.peek();
    }
}
// Return true if end of line (or file)
inline bool skip_check_eol(std::istream &in)
{
    skip_blank(in, false);
    if (!in)
        return false;
    int c = in.peek();
    // Comments count as end of line
    if (c == '#') {
        in.get();
        c = in.peek();
        while (in && c != EOF && c != '\n') {
            in.get();
            c = in.peek();
        }
        return true;
    }
    return (c == EOF || c == '\n');
}

// Skip past blank lines and comments
inline void skip(std::istream &in)
{
    skip_blank(in, true);
    while (in && (in.peek() == '#')) {
        // Skip comment line
        skip_check_eol(in);
        skip_blank(in, true);
    }
}

// Return true if at the end of a record (or file)
inline bool skip_check_eor(std::istream &in)
{
    skip(in);
    int c = in.peek();
    return (c == EOF || c == '.');
}

// Return true if at the end of file
inline bool skip_check_eof(std::istream &in)
{
    skip(in);
    int c = in.peek();
    return (c == EOF);
}

std::ostream &operator<<(std::ostream &out, const ConfigArc &arc)
{
    out << "arc: " << arc.sink << " " << arc.source << std::endl;
    return out;
}

std::istream &operator>>(std::istream &in, ConfigArc &arc)
{
    in >> arc.sink;
    in >> arc.source;
    return in;
}

std::ostream &operator<<(std::ostream &out, const ConfigWord &cw)
{
    out << "word: " << cw.name << " " << to_string(cw.value) << std::endl;
    return out;
}

std::istream &operator>>(std::istream &in, ConfigWord &cw)
{
    in >> cw.name;
    in >> cw.value;
    return in;
}

std::ostream &operator<<(std::ostream &out, const ConfigEnum &cw)
{
    out << "enum: " << cw.name << " " << cw.value << std::endl;
    return out;
}

std::istream &operator>>(std::istream &in, ConfigEnum &ce)
{
    in >> ce.name;
    in >> ce.value;
    return in;
}

std::ostream &operator<<(std::ostream &out, const ConfigUnknown &cu)
{
    out << "unknown: " << to_string(ConfigBit{cu.frame, cu.bit, false}) << std::endl;
    return out;
}

std::istream &operator>>(std::istream &in, ConfigUnknown &cu)
{
    std::string s;
    in >> s;
    ConfigBit c = cbit_from_str(s);
    cu.frame = c.frame;
    cu.bit = c.bit;
    assert(!c.inv);
    return in;
}

std::ostream &operator<<(std::ostream &out, const TileConfig &tc)
{
    for (const auto &arc : tc.carcs)
        out << arc;
    for (const auto &cword : tc.cwords)
        out << cword;
    for (const auto &cenum : tc.cenums)
        out << cenum;
    for (const auto &cunk : tc.cunknowns)
        out << cunk;
    return out;
}

std::istream &operator>>(std::istream &in, TileConfig &tc)
{
    tc.carcs.clear();
    tc.cwords.clear();
    tc.cenums.clear();
    while (!skip_check_eor(in)) {
        std::string type;
        in >> type;
        if (type == "arc:") {
            ConfigArc a;
            in >> a;
            tc.carcs.push_back(a);
        } else if (type == "word:") {
            ConfigWord w;
            in >> w;
            tc.cwords.push_back(w);
        } else if (type == "enum:") {
            ConfigEnum e;
            in >> e;
            tc.cenums.push_back(e);
        } else if (type == "unknown:") {
            ConfigUnknown u;
            in >> u;
            tc.cunknowns.push_back(u);
        } else {
            NPNR_ASSERT_FALSE_STR("unexpected token " + type + " while reading config text");
        }
    }
    return in;
}

void TileConfig::add_arc(const std::string &sink, const std::string &source) { carcs.push_back({sink, source}); }

void TileConfig::add_word(const std::string &name, const std::vector<bool> &value) { cwords.push_back({name, value}); }

void TileConfig::add_enum(const std::string &name, const std::string &value) { cenums.push_back({name, value}); }

void TileConfig::add_unknown(int frame, int bit) { cunknowns.push_back({frame, bit}); }

std::string TileConfig::to_string() const
{
    std::stringstream ss;
    ss << *this;
    return ss.str();
}

TileConfig TileConfig::from_string(const std::string &str)
{
    std::stringstream ss(str);
    TileConfig tc;
    ss >> tc;
    return tc;
}

bool TileConfig::empty() const { return carcs.empty() && cwords.empty() && cenums.empty() && cunknowns.empty(); }

std::ostream &operator<<(std::ostream &out, const ChipConfig &cc)
{
    out << ".device " << cc.chip_name << std::endl << std::endl;
    out << ".variant " << cc.chip_variant << std::endl << std::endl;
    for (const auto &meta : cc.metadata)
        out << ".comment " << meta << std::endl;
    for (const auto &sc : cc.sysconfig)
        out << ".sysconfig " << sc.first << " " << sc.second << std::endl;
    out << std::endl;
    for (const auto &tile : cc.tiles) {
        if (!tile.second.empty()) {
            out << ".tile " << tile.first << std::endl;
            out << tile.second;
            out << std::endl;
        }
    }
    for (const auto &bram : cc.bram_data) {
        out << ".bram_init " << bram.first << std::endl;
        std::ios_base::fmtflags f(out.flags());
        for (size_t i = 0; i < bram.second.size(); i++) {
            out << std::setw(3) << std::setfill('0') << std::hex << bram.second.at(i);
            if (i % 8 == 7)
                out << std::endl;
            else
                out << " ";
        }
        out.flags(f);
        out << std::endl;
    }
    for (const auto &tg : cc.tilegroups) {
        out << ".tile_group";
        for (const auto &tile : tg.tiles) {
            out << " " << tile;
        }
        out << std::endl;
        out << tg.config;
        out << std::endl;
    }
    return out;
}

std::istream &operator>>(std::istream &in, ChipConfig &cc)
{
    while (!skip_check_eof(in)) {
        std::string verb;
        in >> verb;
        if (verb == ".device") {
            in >> cc.chip_name;
        } else if (verb == ".variant") {
            in >> cc.chip_variant;
        } else if (verb == ".comment") {
            std::string line;
            getline(in, line);
            cc.metadata.push_back(line);
        } else if (verb == ".sysconfig") {
            std::string key, value;
            in >> key >> value;
            cc.sysconfig[key] = value;
        } else if (verb == ".tile") {
            std::string tilename;
            in >> tilename;
            TileConfig tc;
            in >> tc;
            cc.tiles[tilename] = tc;
        } else if (verb == ".tile_group") {
            TileGroup tg;
            std::string line;
            getline(in, line);
            std::stringstream ss2(line);

            std::string tile;
            while (ss2) {
                ss2 >> tile;
                tg.tiles.push_back(tile);
            }
            in >> tg.config;
            cc.tilegroups.push_back(tg);
        } else if (verb == ".bram_init") {
            uint16_t bram;
            in >> bram;
            std::ios_base::fmtflags f(in.flags());
            while (!skip_check_eor(in)) {
                uint16_t value;
                in >> std::hex >> value;
                cc.bram_data[bram].push_back(value);
            }
            in.flags(f);
        } else {
            log_error("unrecognised config entry %s\n", verb.c_str());
        }
    }
    return in;
}

NEXTPNR_NAMESPACE_END
