#pragma once
#include "M5GFX.h"
#include <algorithm>
#include <cstdint>

namespace wearable { namespace fonts {
struct SubsetGlyph {
    uint16_t code;
    uint32_t bitmap_offset;
    uint8_t width,height,x_advance;
    int8_t x_offset,y_offset;
};
static_assert(sizeof(SubsetGlyph)==16,"subset glyph ABI is part of the flash budget");

class SubsetFont final : public lgfx::IFont {
public:
    constexpr SubsetFont(const uint8_t* bitmap,const SubsetGlyph* glyphs,uint16_t count,
                         uint8_t ascent,uint8_t descent,uint8_t advance)
        : bitmap_(bitmap),glyphs_(glyphs),count_(count),ascent_(ascent),descent_(descent),advance_(advance) {}
    font_type_t getType() const override {return ft_gfx;}
    void getDefaultMetric(lgfx::FontMetrics* metric) const override {
        metric->width=metric->x_advance=advance_;
        metric->x_offset=0;metric->height=ascent_+descent_;metric->y_advance=advance_;
        metric->y_offset=-ascent_;metric->baseline=ascent_;
    }
    bool updateFontMetric(lgfx::FontMetrics* metric,uint16_t code) const override {
        const auto* glyph=find(code);
        if(!glyph) {metric->width=metric->x_advance=advance_;metric->x_offset=0;return false;}
        metric->width=pgm_read_byte(&glyph->width);
        metric->x_advance=pgm_read_byte(&glyph->x_advance);
        metric->x_offset=int8_t(pgm_read_byte(&glyph->x_offset));
        return true;
    }
    size_t drawChar(lgfx::LGFXBase* gfx,int32_t x,int32_t y,uint16_t code,
                    const lgfx::TextStyle* style,lgfx::FontMetrics* metric,int32_t& filled_x) const override {
        const auto* glyph=find(code);
        if(!glyph) return 0;
        const int32_t sx=65536*style->size_x,sy=65536*style->size_y;
        const int32_t width=pgm_read_byte(&glyph->width),height=pgm_read_byte(&glyph->height);
        const int32_t x_advance=(pgm_read_byte(&glyph->x_advance)*sx)>>16;
        const int32_t x_offset=(int8_t(pgm_read_byte(&glyph->x_offset))*sx)>>16;
        const int32_t y_offset=(-metric->y_offset)+int8_t(pgm_read_byte(&glyph->y_offset));
        const bool fill=style->back_rgb888!=style->fore_rgb888;
        auto converter=gfx->getColorConverter();
        const uint32_t foreground=converter->convert(style->fore_rgb888);
        const uint32_t background=converter->convert(style->back_rgb888);
        const int32_t left=std::max(filled_x,x+(x_offset<0?x_offset:0));
        const int32_t right=x+std::max((width*sx>>16)+x_offset,x_advance);
        if(fill) filled_x=right;
        x+=x_offset;y+=(metric->y_offset*sy)>>16;
        const uint32_t bitmap_offset=pgm_read_dword(&glyph->bitmap_offset);
        gfx->startWrite();
        for(int32_t row=0;row<height;++row) {
            const int32_t y0=((y_offset+row)*sy)>>16;
            const int32_t y1=((y_offset+row+1)*sy)>>16;
            if(fill && left<right) {gfx->setRawColor(background);gfx->writeFillRect(left,y+y0,right-left,std::max<int32_t>(1,y1-y0));}
            int32_t run=-1;
            for(int32_t column=0;column<=width;++column) {
                bool on=false;
                if(column<width) {
                    const uint32_t bit=uint32_t(row)*width+column;
                    on=pgm_read_byte(bitmap_+bitmap_offset+(bit>>3))&(0x80>>(bit&7));
                }
                if(on && run<0) run=column;
                if(!on && run>=0) {
                    const int32_t x0=(run*sx)>>16,x1=(column*sx)>>16;
                    gfx->setRawColor(foreground);gfx->writeFillRect(x+x0,y+y0,std::max<int32_t>(1,x1-x0),std::max<int32_t>(1,y1-y0));run=-1;
                }
            }
        }
        gfx->endWrite();return x_advance;
    }
private:
    const SubsetGlyph* find(uint16_t code) const {
        uint16_t low=0,high=count_;
        while(low<high) {
            const uint16_t middle=low+(high-low)/2;
            const uint16_t value=pgm_read_word(&glyphs_[middle].code);
            if(value<code) low=middle+1;else high=middle;
        }
        return low<count_ && pgm_read_word(&glyphs_[low].code)==code?glyphs_+low:nullptr;
    }
    const uint8_t* bitmap_;const SubsetGlyph* glyphs_;uint16_t count_;
    uint8_t ascent_,descent_,advance_;
};

extern const SubsetFont CjkSc24;
extern const SubsetFont CjkSc28;
extern const SubsetFont WatchTime88;
} } // namespace wearable::fonts
