#include "Atlas.h"
#include "GlyphGeometry.h"
#include "conversion.h"
#include "edge-coloring.h"
#include "import-font.h"
#include "ofFileUtils.h"

namespace ofxMsdfgen {

bool compareFontVariations(const FontVariation & a, const FontVariation & b){
    if(a.name < b.name){
        return true;
    }
    if(a.name > b.name){
        return false;
    }
    if(a.value < b.value){
        return true;
    }
    // now always false
    //if(a.value > b.value){
    //return false;
    //}
    return false;
}

Atlas::Atlas(){
}
Atlas::~Atlas(){
    if(font){
        msdfgen::destroyFont(font);
    }
    msdfgen::deinitializeFreetype(ft);
}

void Atlas::setup(string _fontPath){
    fontPath = _fontPath;
    ft = msdfgen::initializeFreetype();
    font = loadFont(ft, fontPath.c_str());

    variationAxesAvailable.clear();
    vector <msdfgen::FontVariationAxis> _variationAxesAvailable;
    if(msdfgen::listFontVariationAxes(_variationAxesAvailable,
                                      ft,
                                      font)){
        //cout << "found variation axes for " << fontPath << ":" << endl;
        for(const auto & _axis : _variationAxesAvailable){
            FontVariationAxis axis = {
                _axis.name,
                F16DOT16_TO_FLOAT(_axis.minValue),
                F16DOT16_TO_FLOAT(_axis.maxValue),
                F16DOT16_TO_FLOAT(_axis.defaultValue)
            };
            //cout << std::fixed << axis.name << "\n"
            //<< "\tmin:" << axis.minValue
            //<< "\tmax:" << axis.maxValue
            //<< "\t:default:" << axis.defaultValue << endl;
            variationAxesAvailable.push_back(axis);
            addVariations({
                    {axis.name, axis.minValue},
                    {axis.name, axis.maxValue}
                    //{axis.name, axis.defaultValue}
                });
        }
        ofLogNotice("Atlas::setup()") << "retrieved variation axes";
    }else{
        ofLogNotice("Atlas::setup()") << "font does not have variation axes";
    }
    //if(generate()){
    //ofLogNotice("Atlas::setup()") << "generated Atlas";
    //}else{
    //ofLogError("Atlas::setup()") << "whoops, could not generate Atlas";
    //}
}

void Atlas::setup(string _fontPath,
                  AtlasSettings _settings){
    settings = _settings;
    setup(_fontPath);
}

void Atlas::addVariations(vector <FontVariation> variations){
    // add to extremes
    for(const auto & variation : variations){
        auto & v = variationExtremes;

        bool found = false;
        for(const auto & ve : variationExtremes){
            if(ve.name == variation.name && ve.value == variation.value){
                found = true;
            }
        }
        //if(std::find(v.begin(), v.end(), variation) != v.end()){
        if(!found){
            variationExtremes.push_back(variation);
            cout << "variation " << variation.name << " v:" << variation.value << endl;
        }
    }
    // calculate steps
    variationSteps.clear();
    // first, sort the extremes
    sort(variationExtremes.begin(),
         variationExtremes.end(),
         compareFontVariations);

    for(int i = 0; i < variationExtremes.size() - 1; i++){
        const FontVariation & a = variationExtremes[i];
        const FontVariation & b = variationExtremes[i + 1];
        float diff = b.value - a.value;
        int stepAmount = ceil(diff / settings.maxInterpolationStepSize);
        float step = diff / stepAmount;
        float value = a.value + step;
        variationSteps.push_back(a);
        int stop = 0;
        while(value < b.value && stop < 100){
            variationSteps.push_back({a.name, value});
            value += step;
            stop++;
        }
        variationSteps.push_back(b);
    }
    if(variationExtremes.size() == 1){
        variationSteps = variationExtremes;
    }
}

bool Atlas::generate(bool useCache,
                     bool saveToCache){
    cout << "Atlas::generate()" << endl;
    bool success = false;
    bool foundAtlasCacheImage = false;
    if(useCache){
        ofFile atlasPathFile(getAtlasPath());
        if(atlasPathFile.isFile()){
            foundAtlasCacheImage = true;
            if(atlasImage.load(getAtlasPath())){
                foundAtlasCacheImage = true;
            }
        }
    }
    if(ft){
        if(font){
            // Storage for glyph geometry and their coordinates in the atlas
            std::vector <msdf_atlas::GlyphGeometry> glyphs;

            for(const FontVariation & variation : variationSteps){
                std::vector <msdf_atlas::GlyphGeometry> variationGlyphs;
                msdf_atlas::FontGeometry _fontGeometry(&variationGlyphs);
                fontGeometry = _fontGeometry;

                // this function silently fails, if variation is not available
                // which is absolutely fine, and allows to set a random
                // variation step for non-variable fonts.
                msdfgen::setFontVariationAxis(ft,
                                              font,
                                              variation.name.c_str(),
                                              variation.value);
                // Load a set of character glyphs:
                // The second argument can be ignored unless you mix different font sizes in one atlas.
                // In the last argument, you can specify a charset other than ASCII.
                // To load specific glyph indices, use loadGlyphs instead.
                if(settings.characters == "charset::ascii"
                   || settings.characters == ""){
                    fontGeometry.loadCharset(font, 1.0, msdf_atlas::Charset::ASCII);
                }else{
                    msdf_atlas::Charset charset;
                    for(const unsigned char c : settings.characters){
                        msdf_atlas::unicode_t ut(c);
                        charset.add(ut);
                    }
                    //fontGeometry.loadCharset(font, 1.0, charset);
                    //if(glyphs.size() != settings.characters.length()){
                    //fontGeometry.getGlyphs().empty();
                    fontGeometry.loadCharset(font,
                                             1.0,         // fontScale
                                             charset,     // charset
                                             false,       // preprocess geometry
                                             true);       // kerning
                    //}
                }

                int vgi = 0;
                for(const auto & vg : variationGlyphs){
                    const unsigned char cp = vg.getCodepoint();
                    GlyphVariationKey key;
                    key.character = cp;
                    key.variation = variation;
                    glyphGeometryMap[key] = glyphs.size() + vgi;
                    vgi++;
                }

                glyphs.insert(glyphs.end(),
                              variationGlyphs.begin(),
                              variationGlyphs.end());
            }
            // Apply MSDF edge coloring. See edge-coloring.h for other coloring strategies.
            for(msdf_atlas::GlyphGeometry & glyph : glyphs){
                // NOTE: const_cast is potentially evil,
                // but like this we can use clipper for preprocessing shapes
                // msdfgen::Shape & shape = const_cast <msdfgen::Shape &>(glyph.getShape());
                // then convert Shape to ofPolyline and use getResampledBySpacing
                // to convert it into straight lines
                // then use clipper2 to union the shapes
                // convert back into ofPolyline and then Shape
                // or directly into Shape
                //
                // don't do this:
                // shape.inverseYAxis = true;
                glyph.edgeColoring(&msdfgen::edgeColoringSimple, // strategy
                                   settings.maxCornerAngle,       // angleThreshold
                                   0);                            // seed
            }
            // TightAtlasPacker class computes the layout of the atlas.
            msdf_atlas::TightAtlasPacker packer;
            // Set atlas parameters:
            // setDimensions or setDimensionsConstraint to find the best value
            packer.setDimensionsConstraint(msdf_atlas::TightAtlasPacker::DimensionsConstraint::SQUARE);
            // setScale for a fixed size or setMinimumScale to use the largest that fits
            //packer.setMinimumScale(settings.minimumScale);
            packer.setScale(settings.scale);
            // setPixelRange or setUnitRange
            packer.setPixelRange(settings.pixelRange);
            packer.setMiterLimit(settings.miterLimit);
            // Compute atlas layout - pack glyphs
            packer.pack(glyphs.data(), glyphs.size());
            if(!useCache || !foundAtlasCacheImage){
                cout << "did not find atlas image for " << fontPath << ", generating" << endl;
                // Get final atlas dimensions
                int width = 0, height = 0;
                packer.getDimensions(width, height);
                // The ImmediateAtlasGenerator class facilitates the generation of the atlas bitmap.
                msdf_atlas::ImmediateAtlasGenerator <
                    float, // pixel type of buffer for individual glyphs depends on generator function
                    3, // number of atlas color channels
                    & msdf_atlas::msdfGenerator, // function to generate bitmaps for individual glyphs
                    msdf_atlas::BitmapAtlasStorage <float, 3> // class that stores the atlas bitmap
                    // For example, a custom atlas storage class that stores it in VRAM can be used.
                    > generator(width, height);
                // GeneratorAttributes can be modified to change the generator's default settings.
                msdf_atlas::GeneratorAttributes attributes;
                generator.setAttributes(attributes);
                generator.setThreadCount(settings.threadCount);
                // Generate atlas bitmap
                generator.generate(glyphs.data(), glyphs.size());
                // The atlas bitmap can now be retrieved via atlasStorage as a BitmapConstRef.
                // The glyphs array (or fontGeometry) contains positioning data for typesetting text.
                msdfgen::Bitmap <float, 3> bitmap(generator.atlasStorage());
                toOfImage(bitmap, atlasImage);
                atlasImage.update();
                if(saveToCache){
                    ofDirectory dir(getAtlasPathDir());
                    dir.create(true);
                    atlasImage.save(getAtlasPath());
                }
            }

            //std::vector <msdf_atlas::GlyphBox> layout = generator.getLayout();
            //int i = 0;
            //for(const msdf_atlas::GlyphBox & gb : layout){
            //msdf_atlas::GlyphGeometry gg = glyphs[i];
            //i++;
            //glyphGeometries.push_back(gg);
            //}
            for(const msdf_atlas::GlyphGeometry & gg : glyphs){
                glyphGeometries.push_back(gg);
            }
            // Cleanup
            //msdfgen::destroyFont(font);

            success = true;     // FIXME: always turns true, why do we have this
        }
        //msdfgen::deinitializeFreetype(ft);
    }
    return success;
}

const ofImage & Atlas::getAtlasImage(){
    return atlasImage;
}

const vector <GlyphGeometry> & Atlas::getGlyphGeometries(){
    return glyphGeometries;
}

const GlyphGeometry & Atlas::getGlyphGeometry(unsigned char character){
    for(const GlyphGeometry & gg : glyphGeometries){
        if(gg.getCodepoint() == static_cast <msdf_atlas::unicode_t>(character)){
            return gg;
        }
    }
    // not found!
    ofLogError("Atlas::getGlyphGeometry")
        << "glyph(" << character << ") not found!" << endl
        << "returning first glyph. this is bad." << endl;
    // FIXME: proper error handling
    return glyphGeometries[0];
}

const GlyphGeometry & Atlas::getGlyphGeometry(unsigned char character,
                                              FontVariation variation){
    // FIXME: error handling?
    float smallestDistance = FLT_MAX;
    int closestIndex = 0;
    int i = 0;
    for(const auto & v : variationSteps){
        float distance = abs(v.value - variation.value);
        if(distance < smallestDistance){
            smallestDistance = distance;
            closestIndex = i;
        }
        i++;
    }
    const auto & closestVariation = variationSteps[closestIndex];
    GlyphVariationKey key = {character, closestVariation};
    int index = glyphGeometryMap[key];
    return glyphGeometries[index];
}

bool Atlas::getGlyphGeometryPair(const unsigned char character,
                                 const FontVariation variation,
                                 GlyphGeometry & a,
                                 GlyphGeometry & b,
                                 float & mix){
    float smallestDistanceDown = -1 * FLT_MAX;
    float smallestDistanceUp = FLT_MAX;
    int closestIndexDown = 0;
    int closestIndexUp = 0;
    int i = 0;
    for(const auto & v : variationSteps){
        float distance = v.value - variation.value;
        if(distance <= 0){
            if(distance > smallestDistanceDown){
                closestIndexDown = i;
                smallestDistanceDown = distance;
            }
        }else{
            if(distance < smallestDistanceUp){
                closestIndexUp = i;
                smallestDistanceUp = distance;
            }
        }
        i++;
    }

    {
        float total = abs(smallestDistanceUp) + abs(smallestDistanceDown);
        //mix = ofMap(abs(smallestDistanceDown), 0, total, 0, 1);
        mix = total == 0 || smallestDistanceDown == 0 ? 0 : abs(smallestDistanceDown) / total;
    }
    if(mix < 0 || mix > 1){
        ofLogError("Atlas::getGlyphGeometryPair")
            << "mix out of range.\n"
            << "sorry for the shitty error message, just check the code";
        return false;
    }
    const auto & closestVariationUp = variationSteps[closestIndexUp];
    const auto & closestVariationDown = variationSteps[closestIndexDown];
    GlyphVariationKey glyphKeyUp = {character, closestVariationUp};
    GlyphVariationKey glyphKeyDown = {character, closestVariationDown};
    int index_a = glyphGeometryMap[glyphKeyDown];
    int index_b = glyphGeometryMap[glyphKeyUp];
    a = glyphGeometries[index_a];
    b = glyphGeometries[index_b];

    return true;
}

const FontGeometry & Atlas::getFontGeometry(){
    return fontGeometry;
}
const vector <ofxMsdfgen::FontVariationAxis> & Atlas::getVariationAxesAvailable(){
    return variationAxesAvailable;
}

// private
string Atlas::getAtlasPathDir(){
    string fontPathNoData = fontPath;
    ofStringReplace(fontPathNoData, "data/", "");
    string atlasPath = "data/atlascache/" + fontPathNoData + "/msdfgen";
    return atlasPath;
}
string Atlas::getAtlasPath(){
    string atlasPath = getAtlasPathDir() + "/"
                       + ofToString(settings.scale) + "_"
                       + ofToString(settings.minimumScale) + "_"
                       + ofToString(settings.maxInterpolationStepSize);
    for(auto & ve : variationExtremes){
        atlasPath += ve.name + "_" + ofToString(ve.value) + "_";
    }
    atlasPath += ".png";
    cout << "Atlas::getAtlasPath() - atlas path: " << atlasPath << endl;
    return atlasPath;
}
}
