#include <vector>
#include <string>
#include <stdexcept>
#include <cstring>
#include <emscripten/bind.h>

#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"
#include "libraw/libraw.h"

class WASMLibRaw {
public:
	WASMLibRaw() { lraw = new LibRaw(); }
	~WASMLibRaw() { delete lraw; }

	void open(emscripten::val jsBuffer) {
		if (!lraw) throw std::runtime_error("LibRaw not initialized");
        lraw->imgdata.params.output_bps     = 8;
        lraw->imgdata.params.use_camera_wb  = 1;

		std::vector<uint8_t> buffer = toCpp(jsBuffer);
		if (lraw->open_buffer((void*)buffer.data(), buffer.size()) != LIBRAW_SUCCESS) {
            throw std::runtime_error("LibRaw: open_buffer() failed");
        }
        if (lraw->unpack() != LIBRAW_SUCCESS) {
            throw std::runtime_error("LibRaw: unpack() failed");
        }
	}

    emscripten::val imageData() {
        using drawImg = std::unique_ptr<libraw_processed_image_t, std::function<void(libraw_processed_image_t*)>>;
        emscripten::val out = emscripten::val::object();

        // strategy1: use the thumbnail within the image
        if (lraw->unpack_thumb() == LIBRAW_SUCCESS && lraw->imgdata.thumbnail.tformat == LIBRAW_THUMBNAIL_JPEG) {
            drawImg img {
                lraw->dcraw_make_mem_thumb(),
                [this](libraw_processed_image_t* p){ if (p) lraw->dcraw_clear_mem(p); }
            };
            if (img) {
                out.set("preview", true);
                out.set("mime", "image/jpeg");
                out.set("data", toJS(img->data, img->data_size));
                return out;
            }
        }

        // strategy2: unpack the whole thing / slow
        if (int ret = lraw->dcraw_process() != LIBRAW_SUCCESS) {
            throw std::runtime_error("LibRaw: dcraw_process() failed with code " + std::to_string(ret));
        }
        std::vector<uint8_t> bmp;
        drawImg img{
            lraw->dcraw_make_mem_image(),
            [this](libraw_processed_image_t* p){ if (p) lraw->dcraw_clear_mem(p); }
        };
        bmp.reserve(img->width * img->height * img->colors);
        auto stbSink = [](void* ctx, void* data, int size) {
            auto* v = static_cast<std::vector<uint8_t>*>(ctx);
            v->insert(v->end(), static_cast<uint8_t*>(data), static_cast<uint8_t*>(data) + size);
        };
        stbi_write_bmp_to_func(stbSink, &bmp, img->width, img->height, img->colors, img->data);
        out.set("preview",  false);
        out.set("mime", "image/bmp");
        out.set("data", toJS(bmp.data(), bmp.size()));
		return out;
	}

private:
	LibRaw* lraw = nullptr;

	std::vector<uint8_t> toCpp(const emscripten::val& jsBuffer) {
		if (jsBuffer.isNull() || jsBuffer.isUndefined()) return {};
		size_t length = jsBuffer["length"].as<size_t>();
		std::vector<uint8_t> out(length);
        emscripten::val memView = emscripten::val(emscripten::typed_memory_view(length, out.data()));
        memView.call<void>("set", jsBuffer);
		return out;
	}

    emscripten::val toJS(uint8_t* ptr, size_t len) {
        emscripten::val out = emscripten::val::global("Uint8Array").new_(emscripten::val(static_cast<unsigned>(len)));
        out.call<void>("set", emscripten::val(emscripten::typed_memory_view(len, ptr)));
        return out;
    }
};

EMSCRIPTEN_BINDINGS(libraw_module) {
	emscripten::class_<WASMLibRaw>("LibRaw")
		.constructor<>()
		.function("open", &WASMLibRaw::open)
		.function("imageData", &WASMLibRaw::imageData);
}
