// SPDX-License-Identifier: MPL-2.0
#include <iostream>
#include <string>
#include <sstream>
#include <vector>
#include <map>
#include <algorithm>
#include <filesystem>
#include <fstream>
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
using namespace std;
namespace fs=filesystem;
struct color {
    uint8_t r,g,b;
};
struct pixel {
    bool shade_1;
    bool check_1;
    bool shade_2;
    bool check_2;
    bool shade_3;
    bool check_3;
    bool shade_4;
    bool enabled;
};
struct character {
    uint32_t codepoint;
    vector<uint8_t> data;
};
struct fbm_header {
    uint8_t sig[3]={'F','B','M'};
    uint8_t encoding;
    uint32_t charnum;
    uint8_t width;
    uint8_t height;
};
uint32_t charname_to_uint32(string charname) {
    uint32_t value=0;
    stringstream ss;
    ss<<hex<<charname;
    ss>>value;
    int shift=(8-charname.size())*4;
    value<<=shift;
    return value;
}
int get_shade_from_color(const color &c,const color gradient[16]) {
    for (int i=0;i<16;++i) {
        if (gradient[i].r==c.r && gradient[i].g==c.g && gradient[i].b==c.b) {
            return i;
        }
    }
    return -1;
}
uint8_t pixel_to_byte(pixel p) {
    uint8_t out=0x00;
    out|=(uint8_t)p.shade_1<<7;
    out|=(uint8_t)p.check_1<<6;
    out|=(uint8_t)p.shade_2<<5;
    out|=(uint8_t)p.check_2<<4;
    out|=(uint8_t)p.shade_3<<3;
    out|=(uint8_t)p.check_3<<2;
    out|=(uint8_t)p.shade_4<<1;
    out|=(uint8_t)p.enabled;
    return out;
}
map<uint32_t,vector<unsigned char>> chars_data;
vector<string> known_encoding={"ascii","utf8","utf16"};
int main(int argc,char **argv) {
    if (argc!=5) {
        cout<<"[Fontgen] Error: wrong amount of arguments."<<endl;
        return -1;
    }
    string bg_color=string(argv[1]);
    string ft_color=string(argv[2]);
    if (bg_color.size()!=7 || bg_color[0]!='#') {
        cout<<"[Fontgen] Error: invalid background color."<<endl;
        return -1;
    }
    if (ft_color.size()!=7 || ft_color[0]!='#') {
        cout<<"[Fontgen] Error: invalid font color."<<endl;
        return -1;
    }
    for (size_t i=1;i<7;++i) {
        char bg=bg_color[i];
        char ft=ft_color[i];
        if (!isxdigit(static_cast<unsigned char>(bg))) {
            cout<<"[Fontgen] Error: invalid background color."<<endl;
            return -1;
        }
        if (!isxdigit(static_cast<unsigned char>(ft))) {
            cout<<"[Fontgen] Error: invalid font color."<<endl;
            return -1;
        }
    }
    color start,end;
    start.r=static_cast<unsigned char>(std::stoi(bg_color.substr(1,2),nullptr,16));
    start.g=static_cast<unsigned char>(std::stoi(bg_color.substr(3,2),nullptr,16));
    start.b=static_cast<unsigned char>(std::stoi(bg_color.substr(5,2),nullptr,16));
    end.r=static_cast<unsigned char>(std::stoi(ft_color.substr(1,2),nullptr,16));
    end.g=static_cast<unsigned char>(std::stoi(ft_color.substr(3,2),nullptr,16));
    end.b=static_cast<unsigned char>(std::stoi(ft_color.substr(5,2),nullptr,16));
    color gradient[16];
    for (int i=0;i<16;++i) {
      gradient[i].r=start.r+i*(end.r-start.r)/15.0f;
      gradient[i].g=start.g+i*(end.g-start.g)/15.0f;
      gradient[i].b=start.b+i*(end.b-start.b)/15.0f;
    }
    string encoding=string(argv[3]);
    if (find(known_encoding.begin(),known_encoding.end(),encoding)==known_encoding.end()) {
        cout<<"[Fontgen] Error: encoding not supported."<<endl;
        return -1;
    }
    string font_folder=string(argv[4]);
    if (!fs::exists(font_folder)) {
        cout<<"[Fontgen] Error: provided path doesn't exist."<<endl;
        return -1;
    }
    if (!fs::is_directory(font_folder)) {
        cout<<"[Fontgen] Error: provided path isn't a folder."<<endl;
        return -1;
    }
    int width=0,height=0;
    int i=0;
    for (auto const &entry:fs::directory_iterator(font_folder)) {
        if (!entry.is_regular_file()) {
            cout<<"[Fontgen] Error: "<<entry.path()<<" isn't a file."<<endl;
            return -1;
        }
        string filenam=entry.path().filename().string();
        if (filenam.size()<6) {
            cout<<"[Fontgen] Warning: "<<entry.path()<<" filename too short. Skipping."<<endl;
            continue;
        }
        string ext=filenam.substr(filenam.size()-4);
        if (ext!=".png" && ext!=".PNG") {
            cout<<"[Fontgen] Warning: "<<entry.path()<<" is not a PNG. Skipping."<<endl;
            continue;
        }
        if (filenam.size()<5 || filenam.substr(0,2)!="0x") {
            cout<<"[Fontgen] Warning: "<<entry.path()<<" has an ambiguious file name. Skipping."<<endl;
            continue;
        }
        string hexpart=filenam.substr(2,filenam.size()-6);
        if (hexpart.empty() || hexpart.size()>8) {
            cout<<"[Fontgen] Warning: "<<entry.path()<<" has an invalid hex code. Skipping."<<endl;
            continue;
        }
        uint32_t key=charname_to_uint32(hexpart);
        int channel=0,w=0,h=0;
        unsigned char* data=stbi_load(entry.path().string().c_str(),&w,&h,&channel,0);
        if (!data) {
            cout<<"[Fontgen] Error: couldn't read file: "<<filenam<<endl;
            return -1;
        }
        if (i==0) {
            width=w;
            height=h;
        } else {
            if (w!=width || h!=height) {
                cout<<"[Fontgen] Error: all file aren't the same size. File that caused the error: "<<filenam<<endl;
                return -1;
            }
        }
        if (channel!=3) {
            cout<<"[Fontgen] Error: only files with three channels are accepted. File that caused the error: "<<filenam<<endl;
            return -1;
        }
        vector<unsigned char> pixels(data,data+w*h*channel);
        stbi_image_free(data);
        chars_data[key]=std::move(pixels);
        i++;
    }
    vector<character> characters;
    for (auto it:chars_data) {
        character chara;
        chara.codepoint=it.first;
        uint8_t byte1=(chara.codepoint>>24) & 0xFF;
        uint8_t byte2=(chara.codepoint>>16) & 0xFF;
        uint8_t byte3=(chara.codepoint>>8) & 0xFF;
        bool pcheck_1=(byte1>>1) & 1;
        bool pcheck_2=(byte2>>3) & 1;
        bool pcheck_3=(byte3>>5) & 1;
        for (int y=0;y<height;++y) {
            for (int x=0;x<width;++x) {
                int idx=(y*width+x)*3;
                color c;
                c.r=it.second[idx];
                c.g=it.second[idx+1];
                c.b=it.second[idx+2];
                int i=get_shade_from_color(c,gradient);
                if (i==-1 || i>15 || i<0) {
                    cout<<"[Fontgen] Error: found a color that isn't in shade map. Codepoint that caused the error: "<<hex<<setw(2)<<setfill('0')<<it.first<<endl;
                    return -1;
                }
                uint8_t shade=i;
                pixel p{};
                p.shade_1=(shade>>3)&1;
                p.shade_2=(shade>>2)&1;
                p.shade_3=(shade>>1)&1;
                p.shade_4=(shade>>0)&1;
                if (shade!=0) {
                    p.enabled=true;
                } else {
                    p.enabled=false;
                }
                p.check_1=pcheck_1;
                p.check_2=pcheck_2;
                p.check_3=pcheck_3;
                uint8_t byte=pixel_to_byte(p);
                chara.data.push_back(byte);
            }
        }
        characters.push_back(chara);
    }
    fbm_header header;
    header.charnum=(uint32_t)characters.size();
    if (width>255 || width<1) {
        cout<<"[Fontgen] Error: width isn't between 1 and 255."<<endl;
        return -1;
    }
    header.width=width;
    if (height>255 || height<1) {
        cout<<"[Fontgen] Error: height isn't between 1 and 255."<<endl;
        return -1;
    }
    header.height=height;
    if (encoding=="ascii") {
        header.encoding=1;
    } else if (encoding=="utf8") {
        header.encoding=8;
    } else if (encoding=="utf16") {
        header.encoding=16;
    } else {
        header.encoding=255;
    }
    vector<unsigned char> fbm_font;
    size_t charsize=4+header.width*header.height;
    fbm_font.resize(10+header.charnum*charsize);
    memcpy(fbm_font.data(),header.sig,3);
    memcpy(fbm_font.data()+3,&header.encoding,1);
    memcpy(fbm_font.data()+4,&header.charnum,4);
    memcpy(fbm_font.data()+8,&header.width,1);
    memcpy(fbm_font.data()+9,&header.height,1);
    for (int i=0;i<characters.size();++i) {
        size_t char_offset=10+i*charsize;
        memcpy(fbm_font.data()+char_offset,&characters[i].codepoint,4);
        memcpy(fbm_font.data()+char_offset+4,characters[i].data.data(),characters[i].data.size());
    }
    ofstream fileout("font.fbm",ios::binary);
    if (!fileout) {
        cout<<"[Fontgen] Error: couldn't open output file."<<endl;
        return -1;
    }
    fileout.write(reinterpret_cast<const char*>(fbm_font.data()),fbm_font.size());
    fileout.close();
    cout<<"[Fontgen] Successfully generated font.fbm ("<<fbm_font.size()<<" bytes, "<<characters.size()<<" glyphs)"<<endl;
    return 0;
}
