#include <stdio.h>
#include <stdint.h>

#include <json-glib/json-glib.h>

#include <ufo/ufo.h>

#include "ufo-roof.h"
#include "ufo-roof-error.h"
#include "ufo-roof-config.h"

#define roof_config_node_get_with_default(var, parent, type, name, default) do { \
	JsonNode *node = json_object_get_member(parent, name); \
	if (node) var = json_node_get_##type(node); \
	else var = default; \
    } while(0)

#define roof_config_node_get_string_with_default(var, parent, name, default) do { \
	const gchar *str; \
	JsonNode *node = json_object_get_member(parent, name); \
	if (node) str = json_node_get_string(node); \
	else str = default; \
	if (var != str) { \
	    if (var) g_free(var); \
	    var = g_strdup(str); \
	} \
    } while(0)

#define roof_config_node_get(var, parent, type, name) \
    roof_config_node_get_with_default(var, parent, type, name, var)

#define roof_config_node_get_string(var, parent, name) \
    roof_config_node_get_string_with_default(var, parent, name, var)


typedef struct {
    UfoRoofConfig cfg;

    JsonParser *parser;
} UfoRoofConfigPrivate;

void ufo_roof_config_free(UfoRoofConfig *cfg) {
    if (cfg) {
        UfoRoofConfigPrivate *priv = (UfoRoofConfigPrivate*)cfg;

        if (priv->parser)
            g_object_unref (priv->parser);

        free(cfg);
    }
}

UfoRoofConfig *ufo_roof_config_new(const char *config, UfoRoofConfigFlags flags, GError **error) {
    UfoRoofConfigPrivate *priv;
    UfoRoofConfig *cfg;
    
//    JsonNode *node;
    JsonObject *root = NULL;
    JsonObject *hardware = NULL;
    JsonObject *geometry = NULL;
    JsonObject *optics = NULL;
    JsonObject *network = NULL;
    JsonObject *performance = NULL;
    JsonObject *simulation = NULL;
    JsonObject *reconstruction = NULL;
    JsonObject *data = NULL;

    GError *gerr = NULL;
    
    priv = (UfoRoofConfigPrivate*)malloc(sizeof(UfoRoofConfigPrivate));
    if (!priv) roof_new_error(error, "Can't allocate UfoRoofConfig");
    
    memset(priv, 0, sizeof(UfoRoofConfigPrivate));

	// Set defaults
    cfg = &priv->cfg;

    cfg->roof_mode = FALSE;
    cfg->n_planes = 2;
    cfg->n_modules = 16;
    cfg->channels_per_module = 16;
    cfg->bit_depth = 16;
    cfg->samples_per_rotation = 1000;
    cfg->sample_rate = 0;
    cfg->imaging_rate = 0;

    cfg->port = 52067;
    cfg->n_streams = 1;
    cfg->protocol = "udp";
    cfg->network_timeout = 10000000;
    cfg->header_size = sizeof(UfoRoofPacketHeader);
    cfg->payload_size = 0;
    cfg->max_packet_size = 0;
    cfg->max_packets = 100;
    cfg->dataset_size = 0;
    cfg->buffer_size = 2;
    cfg->latency_buffers = 0;
    cfg->drop_buffers = 0;


	// Read configuration
    priv->parser = json_parser_new_immutable ();
    json_parser_load_from_file (priv->parser, config, &gerr);

    if (gerr != NULL) {
        g_propagate_prefixed_error(error, gerr, "Error parsing JSON file (%s) with ROOF configuration: ", config);
        ufo_roof_config_free(cfg);
        return NULL;
    }

    root = json_node_get_object (json_parser_get_root (priv->parser));

    if (root) {
	roof_config_node_get(hardware, root, object, "hardware");
	roof_config_node_get(geometry, root, object, "geometry");
	roof_config_node_get(optics, root, object, "optics");
	roof_config_node_get(network, root, object, "network");
	roof_config_node_get(reconstruction, root, object, "reconstruction");
	roof_config_node_get(performance, root, object, "performance");
	roof_config_node_get(data, root, object, "data");

        if (flags&UFO_ROOF_CONFIG_SIMULATION)
	    roof_config_node_get(simulation, root, object, "simulation");
    }

    if (hardware) {
	roof_config_node_get(cfg->n_planes, hardware, int, "planes");
	roof_config_node_get(cfg->n_modules, hardware, int, "modules");
	roof_config_node_get(cfg->channels_per_module, hardware, int, "channels_per_module");
	roof_config_node_get(cfg->bit_depth, hardware, int, "bit_depth");

	roof_config_node_get(cfg->samples_per_rotation, hardware, int, "samples_per_rotation");
	roof_config_node_get(cfg->sample_rate, hardware, int, "sample_rate");
	roof_config_node_get(cfg->imaging_rate, hardware, int, "imaging_rate");
	
	if ((cfg->sample_rate)||(cfg->imaging_rate)) {
	    if ((!cfg->sample_rate)||(!cfg->imaging_rate)||(cfg->sample_rate%cfg->imaging_rate)) {
		ufo_roof_config_free(cfg);
		roof_new_error(error, "Invalid sample (%u) and imaging (%u) rates are specified", cfg->sample_rate, cfg->imaging_rate);
	    }

	    if ((json_object_get_member(hardware, "samples_per_rotation"))&&(cfg->samples_per_rotation != (cfg->sample_rate / cfg->imaging_rate))) {
		ufo_roof_config_free(cfg);
		roof_new_error(error, "The specified samples-per-rotation (%u) doesn't match   sample/imaging rates (%u / %u)", cfg->samples_per_rotation, cfg->sample_rate, cfg->imaging_rate);
	    }

	    cfg->samples_per_rotation = cfg->sample_rate / cfg->imaging_rate;
	}

	if ((cfg->bit_depth%8)||(cfg->bit_depth > 32)) {
	    ufo_roof_config_free(cfg);
	    roof_new_error(error, "Invalid bit-depth (%u) is configured, only 8, 16, 24, 32 is currently supported", cfg->bit_depth);
	}

	cfg->fan_projections = cfg->samples_per_rotation;
	cfg->fan_bins = cfg->n_modules * cfg->channels_per_module;

	cfg->dataset_size = cfg->fan_projections * cfg->fan_bins * (cfg->bit_depth / 8);
	cfg->n_streams = cfg->n_modules;
	cfg->roof_mode = TRUE;
    }

    if (network) {
	roof_config_node_get(cfg->port, network, int, "port");
	roof_config_node_get(cfg->n_streams, network, int, "streams");

	roof_config_node_get(cfg->payload_size, network, int, "payload_size");
	roof_config_node_get(cfg->header_size, network, int, "header_size");
	roof_config_node_get(cfg->max_packet_size, network, int, "max_packet_size");
        roof_config_node_get(cfg->dataset_size, network, int, "dataset_size");

	if (!cfg->payload_size) {
	    ufo_roof_config_free(cfg);
	    roof_new_error(error, "Packet payload and header size must be set");
	}

	if ((cfg->header_size < sizeof(UfoRoofPacketHeader))&&(!strncmp(cfg->protocol, "udp", 3))) {
            ufo_roof_config_free(cfg);
            roof_new_error(error, "The header with packet id (%lu bytes) is expected for un-ordered protocols", sizeof(UfoRoofPacketHeader));
	}
	
	if (!cfg->dataset_size)
	    cfg->dataset_size = cfg->payload_size;
    }

    if (simulation) {
	roof_config_node_get(cfg->header_size, simulation, int, "header_size");
	
	if (!cfg->payload_size)
	    cfg->payload_size = cfg->dataset_size;
    }

    if (performance) {
	roof_config_node_get(cfg->max_packets, performance, int, "packets_at_once");
	roof_config_node_get(cfg->buffer_size, performance, int, "buffer_size");
	roof_config_node_get(cfg->drop_buffers, performance, int, "drop_buffers");
	roof_config_node_get(cfg->latency_buffers, performance, int, "latency_buffers");
    }


	// Check configuration consistency
    guint fragments_per_dataset = cfg->dataset_size / cfg->payload_size;
    guint fragments_per_stream = fragments_per_dataset / cfg->n_streams;

	// Dataset should be split in an integer number of network packets (we don't expect data from different datasets in one packet at the moment)
    if ((cfg->dataset_size % cfg->payload_size)||(fragments_per_dataset%cfg->n_streams)) {
        ufo_roof_config_free(cfg);
        roof_new_error(error, "Inconsistent ROOF configuration: dataset_size=%u, packet_size=%u, data_streams=%u", cfg->dataset_size, cfg->payload_size, cfg->n_streams);
    }

	// Packet should contain an integer number of complete projections (their parts provided by a single module)
    if ((cfg->roof_mode)&&(cfg->payload_size % (cfg->channels_per_module * (cfg->bit_depth / 8)))) {
        ufo_roof_config_free(cfg);
        roof_new_error(error, "Inconsistent ROOF configuration: packet_size=%u, projection_size=%u (%u channels x %u bits)", cfg->payload_size, cfg->channels_per_module * (cfg->bit_depth / 8), cfg->channels_per_module, cfg->bit_depth);
    }

    if (!cfg->max_packet_size) 
	cfg->max_packet_size = cfg->header_size + cfg->payload_size;

    if (hardware) {
	if (cfg->n_modules != cfg->n_streams) {
	    ufo_roof_config_free(cfg);
	    roof_new_error(error, "Currently, number of ROOF modules (%u) is exepcted to be equal to number of independent data streams (%u)", cfg->n_modules, cfg->n_streams);
	}

	if (cfg->dataset_size != (cfg->fan_projections * cfg->fan_bins * cfg->bit_depth / 8)) {
	    ufo_roof_config_free(cfg);
	    roof_new_error(error, "Specified dataset size (%u) does not match ROOF configuration (modules: %u, channels-per-module: %u, bit-depth: %u, samples-per-rotation: %u)", cfg->dataset_size, cfg->n_modules, cfg->channels_per_module, cfg->bit_depth, cfg->samples_per_rotation);
	}
    }

    if ((cfg->buffer_size * fragments_per_stream) < cfg->max_packets) {
        cfg->max_packets = cfg->buffer_size * fragments_per_stream / 2;
    }

    if (cfg->buffer_size < 4) {
	cfg->drop_buffers = 0;
    } else if (cfg->drop_buffers >= cfg->buffer_size) {
	cfg->drop_buffers = cfg->buffer_size / 2;
    }

    return cfg;
}