#include "main.hpp"
#include "woff.hpp"
#include "io.hpp"
#include <vector>


config_s config = {};

static void usage(const char* name) {
    LOG(
        "usage: %s [-v] [-d] [-e|-i range1[,range2[,...]]] [-a range1[,range2[,...]] -b num] infile.woff [outfile.woff]\n"
        "       -v: be verbose (to stderr)\n"
        "       -d: dump woff information (to stdout)\n"
        "       -e: exclude/strip following ranges from input file\n"
        "       -i: include/keep only following ranges from input file\n"
        "       -a: align character bounding boxes to a determined minimum baseline (can be combined with -i or -e)\n"
        "           for an empty range argument, all (leftover) characters are assumed\n"
        "       -b: when aligning, use this y-coordinate above the baseline instead (> 0)\n"
        "       ranges are a list of ASCII/UTF character codes in hex notation, e.g: 20-7e,F001-F008,E12a"
        , name
    );
}

static bool parse_range_list(std::vector<char_range_t>& v, char* a) {
    char* p = a;
    while (*p) {
        char* e = strchr(p, ',');
        if (e) *e = '\0';
        char* d = strchr(p, '-');
        if (d) *d = '\0';

        char_t from, to;
        if (sscanf(p, "%x", &from) != 1) return false;
        if (d) {
            if (sscanf(d+1, "%x", &to) != 1) return false;
        } else {
            to = from;
        }
        v.push_back((char_range_t){from, to});

        if (!e) break;
        p = e+1;
    }
    return true;
}

int main(int argc, char** argv) {
    bool charcodes_exclude = false;
    bool charcodes_set = false;
    std::vector<char_range_t> charcodes;
    int align_to = 0;
    bool align_charcodes_set = false;
    std::vector<char_range_t> align_charcodes;

    int opt;
    while ((opt = getopt(argc, argv, "vde:i:a:b:")) != -1) {
        switch (opt) {
            case 'v':
                config.verbose = true;
                break;
            case 'd':
                config.dump = true;
                break;
            case 'e':
            case 'i':
                charcodes_exclude = (opt == 'e');
                if (charcodes_set || !parse_range_list(charcodes, optarg)) {
                    usage(argv[0]);
                    return 1;
                }
                charcodes_set = true;
                break;
            case 'a':
                if (align_charcodes_set || !parse_range_list(align_charcodes, optarg)) {
                    usage(argv[0]);
                    return 1;
                }
                align_charcodes_set = true;
                break;
            case 'b':
                if (align_to || (align_to = atoi(optarg)) <= 0) {
                    usage(argv[0]);
                    return 1;
                }
                break;
            default:
                usage(argv[0]);
                return 1;
        }
    }
    if (optind+2 < argc) {
        usage(argv[0]);
        return 1;
    }
    const char* infile = (optind <= argc)? argv[optind]: NULL;
    const char* outfile = (optind < argc)? argv[optind+1]: NULL;

    char* buf;
    size_t len;
    if (!infile) {
        usage(argv[0]);
        return 1;
    }
    if (!file_read(infile, buf, len)) {
        return 1;
    }

    Woff woff(buf, len);
    if (!woff.parseHeader()) {
        LOG("cannot parse header");
        return 1;
    }
    if (!woff.parseTables()) {
        LOG("cannot parse tables");
        return 1;
    }
    if (!woff.parseCharMaps()) {
        LOG("cannot parse character maps");
        return 1;
    }
    LOG("found %zu chars", woff.getCharMap().size());
    if (!woff.parseLoca()) {
        LOG("cannot parse character indices");
        return 1;
    }

    std::vector<char_range_t> remainders;
    if (charcodes_set) {
        if (charcodes_exclude) {
            woff.getCharMap().set_intersect(charcodes, remainders);
        } else {
            woff.getCharMap().set_substract(charcodes, remainders);
        }
    } else {
        std::vector<char_range_t> tmp;
        woff.getCharMap().set_intersect(tmp, remainders);
    }
    if (align_charcodes_set) {
        if (align_charcodes.empty()) {
            align_charcodes.swap(remainders);
        } else {
            Cmaps::intersect(remainders, align_charcodes);
        }
    }
    remainders.clear();

    if (charcodes.empty()) {
        LOG("not removing any char glyphs");
    } else {
        LOG("removing %zu char glyphs", charcodes.size());
        for (std::vector<char_range_t>::const_iterator it=charcodes.begin(); it!=charcodes.end(); ++it) {
            for (char_t c=it->from; c<=it->to; ++c) {
                index_t index = woff.getCharMap().find(c);
                assert(index); // buggy set operation otherwise
                LOG_INFO("char %04x @ %u", c, index);
                if (!woff.deleteCharIndex(index)) {
                    LOG("cannot delete char %04x at index %u", c, index);
                    return 1;
                }
            }
        }
    }

    if (align_charcodes.empty()) {
        LOG("not aligning any char glyphs");
    } else {
        assert(align_to >= 0);
        align_to = woff.getMinAlignment(align_charcodes, (unsigned)align_to);
        if (!align_to) {
            LOG("cannot infer or validate baseline alignment");
            return 1;
        }
        LOG("aligning %zu char glyphs to %d", align_charcodes.size(), align_to);
        for (std::vector<char_range_t>::const_iterator it=align_charcodes.begin(); it!=align_charcodes.end(); ++it) {
            for (char_t c=it->from; c<=it->to; ++c) {
                index_t index = woff.getCharMap().find(c);
                LOG_INFO("char %04x @ %u", c, index);
                if (!woff.alignCharIndex(index, (unsigned)align_to)) {
                    LOG("cannot align char %04x", c);
                    return 1;
                }
            }
        }
    }

    if (charcodes.empty() && align_charcodes.empty()) {
        LOG("nothing to do");
        return 0;
    }

    if (!woff.finalize()) return 1;
    if (!outfile) return 0;

    size_t outlen;
    char* outbuf = woff.toBuf(outlen);
    if (!outbuf) {
        LOG("cannot pack result");
        return 1;
    }
    if (!file_write(outfile, outbuf, outlen)) {
        free(outbuf);
        return 1;
    }
    free(outbuf);
    LOG("wrote to '%s' - done.", outfile);

    return 0;
}