#include "cmaps.hpp"


bool Cmaps::parse0(const char* b, size_t l, std::map<char_t, index_t>& map) {
    if (l < sizeof(WoffCmap0)) return false;
    const WoffCmap0* cmap = (const WoffCmap0*)b;
    cmap->print("  ", "charmap 0");

    assert(map.empty());
    for (char_t i=0; i<sizeof(cmap->glyphIndexArray); ++i) {
        if (cmap->glyphIndexArray[i] != 0) {
            map.insert(std::pair<char_t, index_t>(i, cmap->glyphIndexArray[i]));
        }
    }
    return true;
}


bool Cmaps::parse4(const char* b, size_t l, std::map<char_t, index_t>& map) {
    if (l < sizeof(WoffCmap4)) return false;
    WoffCmap4* cmap = (WoffCmap4*)b;
    cmap->print("  ", "charmap 4");

    uint16_t segCount = w2uint16(cmap->segCountX2)/2;
    if (l < sizeof(WoffCmap4) + (segCount * sizeof(wuint16_t) * 4) + sizeof(wuint16_t)) return false;
    wuint16_t* endCode = (wuint16_t*)(cmap + 1);
    wuint16_t* startCode = (wuint16_t*)((char*)endCode + segCount*sizeof(wuint16_t) + sizeof(wuint16_t));
    wuint16_t* idDelta = (wuint16_t*)((char*)startCode + segCount*sizeof(wuint16_t));
    wuint16_t* idRangeOffset = (wuint16_t*)((char*)idDelta + segCount*sizeof(wuint16_t));
    //wuint16_t* glyphIndexArray = (wuint16_t*)((char*)idRangeOffset + segCount*sizeof(wuint16_t));

    for (uint16_t s=0; s<segCount; ++s) {
        LOG_DUMP("    %04x-%04x (@ %u+%u)", w2uint16(startCode[s]), w2uint16(endCode[s]), w2uint16(idDelta[s]), w2uint16(idRangeOffset[s]));
        if (w2uint16(startCode[s]) > w2uint16(endCode[s])) return false;
        for (char_t c=(char_t)w2uint16(startCode[s]); c<=(char_t)w2uint16(endCode[s]); ++c) {
            index_t index;
            if (w2uint16(idRangeOffset[s]) == 0) {
                index = c;
            } else {
                uint16_t off = (c - w2uint16(startCode[s])); // * sizeof(uint16_t) but is already pointer arithmetic
                off += w2uint16(idRangeOffset[s]) / 2;
                if (s + off > l - ((const char*)idRangeOffset - b)) return false;
                index = w2uint16(idRangeOffset[s + off]);
            }
            index = (index + w2uint16(idDelta[s])) % 65536u;
            if (!index) continue;

            LOG_DUMP("      %04x: %u", c, index);
            if (map.find(c) != map.end()) return false;
            map.insert(std::pair<char_t, index_t>(c, index));
        }
    }

    return true;
}


bool Cmaps::parse12(const char* b, size_t l, std::map<char_t, index_t>& map) {
    if (l < sizeof(WoffCmap12)) return false;
    WoffCmap12* cmap = (WoffCmap12*)b;
    cmap->print("  ", "charmap 12");

    if (l < sizeof(WoffCmap12) + w2uint32(cmap->nGroups) * sizeof(WoffCmap12Group)) return false;
    for (uint32_t g=0; g<w2uint32(cmap->nGroups); ++g) {
        WoffCmap12Group* group = ((WoffCmap12Group*)(cmap+1)) + g;
        group->print("    ");

        if (w2uint32(group->startCharCode) > w2uint32(group->endCharCode)) return false;
        for (char_t c=w2uint32(group->startCharCode); c<=w2uint32(group->endCharCode); ++c) {
            index_t i = w2uint32(group->startGlyphCode) + (c - w2uint32(group->startCharCode));
            if (map.find(c) != map.end()) return false;
            map.insert(std::pair<char_t, index_t>(c, i));
        }
    }
    return true;
}


bool Cmaps::parse(const char* buf, size_t len) {
    charmap.clear();

    if (len < sizeof(WoffCmapIndex)) return false;
    const WoffCmapIndex* index = (WoffCmapIndex*)buf;
    index->print("  ", "character map index");

    if (len < sizeof(WoffCmapIndex) + w2uint16(index->numberSubtables) * sizeof(WoffCmapSubtable)) return false;
    const WoffCmapSubtable* subtable = (WoffCmapSubtable*)(index+1);
    for (unsigned si=0; si<w2uint16(index->numberSubtables); ++si) {
        subtable[si].print("  ", "character map subtable");

        if (w2uint32(subtable[si].offset) + sizeof(uint16_t) >= len) return false;
        unsigned format = w2uint16(*((uint16_t*)(buf + w2uint32(subtable[si].offset)))); // peek into format

        std::map<char_t, index_t> submap;
        size_t cmaplen = len - w2uint32(subtable[si].offset); // not ordered: ((si == w2uint16(index->numberSubtables)-1)? (uint32_t)len: w2uint32(subtable[si+1].offset)) - w2uint32(subtable[si].offset);
        if (format == 0) {
            if (!parse0(buf + w2uint32(subtable[si].offset), cmaplen, submap)) {
                return false;
            }
        } else if (format == 4) {
            if (!parse4(buf + w2uint32(subtable[si].offset), cmaplen, submap)) {
                return false;
            }
        } else if (format == 12) {
            if (!parse12(buf + w2uint32(subtable[si].offset), cmaplen, submap)) {
                return false;
            }
        } else {
            LOG("unsupported cmap format %u", format);
            return false;
        }

        for (std::map<char_t, index_t>::iterator it=submap.begin(); it!=submap.end(); ++it) {
            assert(it->second != 0);
            std::map<char_t, index_t>::iterator c = charmap.find(it->first);
            if (c == charmap.end()) {
                charmap.insert(std::pair<char_t, index_t>(it->first, it->second));
            } else if (c->second != it->second) {
                LOG("char index conflict");
                return false;
            }
        }
    }

    LOG_DUMP("charmap");
    for (std::map<char_t, index_t>::const_iterator it=charmap.begin(); it!=charmap.end(); ++it) {
        LOG_DUMP("  %04x @ %u", it->first, it->second);
    }
    return true;
}


index_t Cmaps::find(char_t c) const {
    std::map<char_t, index_t>::const_iterator cit = charmap.find(c);
    if (cit == charmap.end()) {
        return 0;
    } else {
        assert(cit->second != 0);
        return cit->second;
    }
}


void Cmaps::set_op(std::vector<char_range_t>& v, std::vector<char_range_t>& rem, bool get_given) const {
    assert(rem.empty());
    std::vector<char_range_t> rv;
    for (std::map<char_t, index_t>::const_iterator it=charmap.begin(); it!=charmap.end(); ++it) {
        const char_t& c = it->first;
        bool given = false;
        for (std::vector<char_range_t>::iterator vit=v.begin(); vit!=v.end(); ++vit) {
            assert(vit->from <= vit->to);
            if (c >= vit->from && c <= vit->to) {
                given = true;
                if (vit->from == vit->to) v.erase(vit);
                break;
            }
        }
        if (given == get_given) {
            rv.push_back((char_range_t){c, c});
        } else {
            rem.push_back((char_range_t){c, c});
        }
    }
    v.swap(rv);
}


void Cmaps::set_intersect(std::vector<char_range_t>& v, std::vector<char_range_t>& rem) const {
    set_op(v, rem, true);
}


void Cmaps::set_substract(std::vector<char_range_t>& v, std::vector<char_range_t>& rem) const {
    set_op(v, rem, false);
}


void Cmaps::intersect(const std::vector<char_range_t>& v, std::vector<char_range_t>& r) {
    std::vector<char_range_t> rv;
    for (std::vector<char_range_t>::iterator rit=r.begin(); rit!=r.end(); ++rit) {
        assert(rit->from <= rit->to);
        for (char_t c=rit->from; c<=rit->to; ++c) {
            for (std::vector<char_range_t>::const_iterator vit=v.begin(); vit!=v.end(); ++vit) {
                assert(vit->from <= vit->to);
                if (c >= vit->from && c <= vit->to) {
                    bool found = false;
                    for (std::vector<char_range_t>::const_iterator it=rv.begin(); it!=rv.end(); ++it) {
                        if (c >= it->from && c <= it->to) found = true;
                    }
                    if (!found) {
                        rv.push_back((char_range_t){c, c});
                    }
                    break;
                }
            }
        }
    }
    r.swap(rv);
}