#include "conf.hpp"
#include "objects.hpp"
#include "shaders.hpp"
#include "lights.hpp"
#include "texture.hpp"
#include "img.hpp"


pan_s pan = {}; // NOLINT(cert-err58-cpp)


static INLINE bool parse(char* s, vertex_t& v) {
    return s && sscanf(s, " ( %" COORD_FORMAT " , %" COORD_FORMAT " , %" COORD_FORMAT " ) " , &v.x, &v.y, &v.z) == 3;
}


static INLINE bool parse(char* s, coord_t& c) {
    return s && sscanf(s, " %" COORD_FORMAT " ", &c) == 1;
}


static INLINE bool parse(char* s, unsigned& u) {
    return s && sscanf(s, " %u ", &u) == 1;
}


static INLINE bool parse(char* s, Img::rgb_t& c) {
    unsigned r, g, b;
    if (!s || sscanf(s, " 0x%02x%02x%02x ", &r, &g, &b) != 3) return false;
    c.r = r; c.g = g; c.b = b;
    return true;
}


static INLINE bool parse(char* s, Texture*& t) {
    unsigned r = 0;
    char* p;
    if (*s >= '0' && *s <= '9' && (p = strchr(s, '+')) != NULL) {
        *p = '\0';
        r = atoi(s);
        s = p+1;
    }

    Img::rgb_t c1;
    if (!s) {
        return false;
    } else if (parse(s, c1)) {
        t = new ColorTexture(r, c1);
        return true;
    } else {
        char* mod = strrchr(s, ':');
        if (mod) {
            *(mod++) = '\0';
        }
        t = ImageTexture::load(r, s, mod);
        return (t != NULL);
    }
}


static INLINE bool parse(char* n, char* v) {
    if (!n || !v) return false;
    for (unsigned i=0; i<sizeof(config)/sizeof(config_t::val_t); ++i) {
        config_t::val_t* val = &(((config_t::val_t*)&config)[i]);
        if (!strcmp(n, val->name)) {
            val->i = atoi(v);
            val->d = atof(v);
            return true;
        }
    }
    return false;
}


static INLINE bool parse_line(char* l, Scene*& scene, Camera*& camera) {
    coord_t a;
    unsigned d, e;
    vertex_t u, v, w;
    Texture* t;
    Img::rgb_t c;
    char* tok = strtok(l, " \t");
    if (!tok || *tok == '#') {
        return true;
    } else if (!strcmp(tok, "sphere")) {
        if (!parse(strtok(NULL, " \t"), t)) return false;
        if (!parse(strtok(NULL, " \t"), u)) return false;
        if (!parse(strtok(NULL, " \t"), a)) return false;
        Sphere* s = new Sphere(t, u, a);
        if (!scene->push(s)) return false;
    } else if (!strcmp(tok, "plane")) {
        if (!parse(strtok(NULL, " \t"), t)) return false;
        if (!parse(strtok(NULL, " \t"), u)) return false;
        if (!parse(strtok(NULL, " \t"), v)) return false;
        if (!parse(strtok(NULL, " \t"), w)) return false;
        if (!scene->push(new Plane(t, u, v, w))) return false;
    } else if (!strcmp(tok, "axisplane")) {
        if (!parse(strtok(NULL, " \t"), t)) return false;
        if (!parse(strtok(NULL, " \t"), u)) return false;
        if (!parse(strtok(NULL, " \t"), v)) return false;
        if (!scene->push(new AxisPlane(t, u, v))) return false;
    } else if (!strcmp(tok, "cylinder")) {
        if (!parse(strtok(NULL, " \t"), t)) return false;
        if (!parse(strtok(NULL, " \t"), u)) return false;
        if (!parse(strtok(NULL, " \t"), v)) return false;
        if (!parse(strtok(NULL, " \t"), a)) return false;
        if (!scene->push(new Cylinder(t, u, v, a))) return false;
    } else if (!strcmp(tok, "globallight")) {
        if (!parse(strtok(NULL, " \t"), d)) return false;
        if (!scene->push(new GlobalLight(d))) return false;
    } else if (!strcmp(tok, "raylight")) {
        if (!parse(strtok(NULL, " \t"), d)) return false;
        if (!scene->push(new RayLight(d))) return false;
    } else if (!strcmp(tok, "pointlight")) {
        if (!parse(strtok(NULL, " \t"), u)) return false;
        if (!parse(strtok(NULL, " \t"), c)) return false;
        if (!parse(strtok(NULL, " \t"), d)) return false;
        if (!parse(strtok(NULL, " \t"), e)) return false;
        if (!scene->push(new PointLight(u, c, d, e))) return false;
    } else if (!strcmp(tok, "arealight")) {
        if (!parse(strtok(NULL, " \t"), u)) return false;
        if (!parse(strtok(NULL, " \t"), v)) return false;
        if (!parse(strtok(NULL, " \t"), d)) return false;
        if (!parse(strtok(NULL, " \t"), e)) return false;
        if (!scene->push(new AreaLight(u, v, d, e))) return false;
    } else if (!strcmp(tok, "depthfog")) {
        if (!parse(strtok(NULL, " \t"), c)) return false;
        if (!parse(strtok(NULL, " \t"), a)) return false;
        if (!parse(strtok(NULL, " \t"), d)) return false;
        new DepthFog(c, a, d);
    } else if (!strcmp(tok, "depthblur")) {
        if (!parse(strtok(NULL, " \t"), d)) return false;
        if (!parse(strtok(NULL, " \t"), e)) return false;
        new DepthBlur(d, e);
    } else if (!strcmp(tok, "volumetriclight")) {
        if (!parse(strtok(NULL, " \t"), u)) return false;
        if (!parse(strtok(NULL, " \t"), d)) return false;
        if (!parse(strtok(NULL, " \t"), e)) return false;
        new VolumetricLight(u, d, e);
    } else if (!strcmp(tok, "camera")) {
        if (!parse(strtok(NULL, " \t"), u)) return false;
        if (!parse(strtok(NULL, " \t"), v)) return false;
        if (!parse(strtok(NULL, " \t"), d)) return false;
        if (!parse(strtok(NULL, " \t"), e)) return false;
        if (camera) return false;
        camera = new Camera(u, v, d, e);
    } else if (!strcmp(tok, "set")) {
        char* name = strtok(NULL, " \t");
        char* val = strtok(NULL, " \t");
        if (!parse(name, val)) return false;
    } else if (!strcmp(tok, "pan")) {
        if (!parse(strtok(NULL, " \t"), pan.frames)) return false;
        if (!parse(strtok(NULL, " \t"), pan.camera_move)) return false;
        if (!parse(strtok(NULL, " \t"), pan.camera_pan)) return false;
    } else {
        return false;
    }
    return true;
}


bool parse_file(const char* fn, Scene*& scene, Camera*& camera) {
    size_t len;
    char* buf = file_read(fn, len);
    if (!buf) return false;

    scene = new Scene();
    camera = NULL;

    unsigned lineno = 0;
    char* p = buf;
    char* nl;
    while ((nl = strchr(p, '\n'))) {
        *nl = '\0';
        lineno++;
        if (!parse_line(p, scene, camera)) {
            LOG("cannot parse '%s' line %u ('%s')", fn, lineno, p);
            delete scene;
            if (camera) {
                delete camera;
            }
            free(buf);
            return false;
        }
        p = nl+1;
    }
    free(buf);

    if (!camera) {
        delete scene;
        return false;
    }

    if (!pan.frames) {
        delete scene;
        delete camera;
        return false;
    }

    scene->finalize();
    return true;
}