#include "plot.hpp"
#include <SDL2/SDL.h>
#include <SDL2/SDL_ttf.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/select.h>
#include <unistd.h>
#include <fcntl.h>
#include <cerrno>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <map>
#include <sstream>
#include <iostream>

// Font data and font instance are provided by plot.cpp.
extern const unsigned char vera_ttf[];
extern const unsigned int vera_ttf_size;
extern TTF_Font* font;
void PlotGlyphCacheClear();

static bool auto_present = false;

// Hex encoding keeps UTF-8 text (including spaces and newlines) on one protocol line.
static bool decode_hex(const std::string& hex, std::string& out) {
    if (hex == "-") { out.clear(); return true; }
    if (hex.empty() || hex.size() % 2 != 0 || hex.size() > 8192) return false;
    auto digit = [](char c) -> int {
        if (c >= '0' && c <= '9') return c - '0';
        if (c >= 'a' && c <= 'f') return c - 'a' + 10;
        if (c >= 'A' && c <= 'F') return c - 'A' + 10;
        return -1;
    };
    out.clear();
    for (size_t i = 0; i < hex.size(); i += 2) {
        int hi = digit(hex[i]), lo = digit(hex[i + 1]);
        if (hi < 0 || lo < 0 || (hi == 0 && lo == 0)) return false;
        out.push_back(static_cast<char>((hi << 4) | lo));
    }
    return true;
}

static bool execute(const std::string& line) {
    std::istringstream s(line);
    std::string cmd; s >> cmd;
    if (cmd == "CLEAR") { int r,g,b; if (!(s>>r>>g>>b)) return false; PlotBackground(r,g,b); }
    else if (cmd == "COLOR") { int r,g,b; if (!(s>>r>>g>>b)) return false; PlotColor(r,g,b); }
    else if (cmd == "PIXEL") { double x,y; if (!(s>>x>>y)) return false; PlotPixel(x,y); }
    else if (cmd == "LINE") { double a,b,c,d; if (!(s>>a>>b>>c>>d)) return false; PlotLine(a,b,c,d); }
    else if (cmd == "RECT") { double a,b,c,d; if (!(s>>a>>b>>c>>d)) return false; PlotRectangle(a,b,c,d); }
    else if (cmd == "FILLRECT") { double a,b,c,d; if (!(s>>a>>b>>c>>d)) return false; PlotFilledRectangle(a,b,c,d); }
    else if (cmd == "CROSS") { PlotCross(); }
    else if (cmd == "COLORA") { int r,g,b,a; if (!(s>>r>>g>>b>>a)) return false; if (r<0||r>255||g<0||g>255||b<0||b>255||a<0||a>255) return false; PlotColor(r,g,b,a); }
    else if (cmd == "TRIANGLE") { double x1,y1,x2,y2,x3,y3; if (!(s>>x1>>y1>>x2>>y2>>x3>>y3)) return false; PlotTriangle({x1,y1},{x2,y2},{x3,y3}); }
    else if (cmd == "TEXT") { double x,y; std::string hex, value; if (!(s>>x>>y>>hex) || !decode_hex(hex,value)) return false; PlotText(x,y,"%s",value.c_str()); }
    else if (cmd == "PRESENT") SDL_RenderPresent(g_renderer);
    else if (cmd == "AUTOPRESENT") {
        int enabled;
        if (!(s >> enabled) || (enabled != 0 && enabled != 1)) return false;
        auto_present = enabled != 0;
        if (auto_present) SDL_RenderPresent(g_renderer);
    }
    else if (cmd == "MODE") { int m; if (!(s>>m) || m<0 || m>2) return false; PlotMode(m); }
    else if (cmd == "QUIT") { return true; }
    else return false;
    std::string extra;
    if (s >> extra) return false;
    if (auto_present && cmd != "PRESENT" && cmd != "AUTOPRESENT" &&
        cmd != "QUIT" && cmd != "COLOR" && cmd != "COLORA" && cmd != "MODE") {
        SDL_RenderPresent(g_renderer);
    }
    return true;
}
int main(int argc, char** argv) {
    const char* path = argc > 1 ? argv[1] : "/tmp/plot-lisp.sock";
    int w = argc > 2 ? std::atoi(argv[2]) : 800;
    int h = argc > 3 ? std::atoi(argv[3]) : 600;
    if (w<=0 || h<=0 || std::strlen(path)>=sizeof(sockaddr_un::sun_path)) return 1;
    if (SDL_Init(SDL_INIT_VIDEO)!=0) { std::cerr << SDL_GetError() << '\n'; return 1; }
    if (TTF_Init() != 0) { std::cerr << TTF_GetError() << '\n'; SDL_Quit(); return 1; }
    SDL_RWops* rw = SDL_RWFromConstMem(vera_ttf, static_cast<int>(vera_ttf_size));
    if (!rw) { std::cerr << SDL_GetError() << '\n'; TTF_Quit(); SDL_Quit(); return 1; }
    font = TTF_OpenFontRW(rw, 1, 20);
    if (!font) { std::cerr << TTF_GetError() << '\n'; TTF_Quit(); SDL_Quit(); return 1; }
    TTF_SetFontKerning(font, 1);
    TTF_SetFontHinting(font, TTF_HINTING_NORMAL);
    PlotCanvas(w,h,0);
    if (!g_window || !g_renderer) { std::cerr << SDL_GetError() << '\n'; SDL_Quit(); return 1; }
    SDL_SetWindowTitle(g_window,"Plot Lisp");
    PlotColor(0,0,0); PlotBackground(255,255,255); SDL_RenderPresent(g_renderer);
    int listener = socket(AF_UNIX,SOCK_STREAM,0);
    if (listener<0) { perror("socket"); return 1; }
    sockaddr_un addr{}; addr.sun_family=AF_UNIX; std::strncpy(addr.sun_path,path,sizeof(addr.sun_path)-1);
    // Do not unlink a socket belonging to another running server.
    if (access(path,F_OK)==0) { std::cerr << "Socket path already exists: " << path << '\n'; return 1; }
    if (bind(listener,(sockaddr*)&addr,sizeof(addr))<0 || listen(listener,8)<0) { perror("bind/listen"); close(listener); return 1; }
    std::cout << "READY " << path << std::endl;
    std::map<int,std::string> clients;
    bool running=true;
    while(running) {
        SDL_Event e; while(SDL_PollEvent(&e)) if(e.type==SDL_QUIT) running=false;
        fd_set rfds; FD_ZERO(&rfds); FD_SET(listener,&rfds); int maxfd=listener;
        for(auto& kv:clients) { FD_SET(kv.first,&rfds); if(kv.first>maxfd) maxfd=kv.first; }
        timeval tv{0,10000};
        int n=select(maxfd+1,&rfds,nullptr,nullptr,&tv);
        if(n<0) { if(errno==EINTR) continue; perror("select"); break; }
        if(FD_ISSET(listener,&rfds)) { int fd=accept(listener,nullptr,nullptr); if(fd>=0) clients[fd]=""; }
        for(auto it=clients.begin();it!=clients.end();) {
            int fd=it->first; bool drop=false;
            if(FD_ISSET(fd,&rfds)) {
                char buf[4096]; ssize_t len=read(fd,buf,sizeof(buf));
                if(len<=0) drop=true;
                else {
                    it->second.append(buf,len);
                    if(it->second.size()>65536) drop=true;
                    while(!drop) {
                        size_t p=it->second.find('\n'); if(p==std::string::npos) break;
                        std::string line=it->second.substr(0,p); it->second.erase(0,p+1);
                        bool ok=execute(line);
                        if(line=="QUIT") running=false;
                        const char* reply=ok?"OK\n":"ERR\n";
                        if(write(fd,reply,std::strlen(reply))<0) { drop=true; break; }
                    }
                }
            }
            if(drop) { close(fd); it=clients.erase(it); } else ++it;
        }
    }
    for(auto& kv:clients) close(kv.first);
    close(listener); unlink(path);
    PlotGlyphCacheClear();
    TTF_CloseFont(font);
    font = nullptr;
    SDL_DestroyRenderer(g_renderer); SDL_DestroyWindow(g_window);
    TTF_Quit(); SDL_Quit();
    return 0;
}
