#include "dcp.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

unsigned int
get_int32(const unsigned char *buffer, size_t offset)
{
        return (buffer[offset + 3] << 24) | (buffer[offset + 2] << 16) |
               (buffer[offset + 1] << 8) | (buffer[offset]);
}

unsigned int
get_int16(const unsigned char *buffer, size_t offset)
{
        return (buffer[offset + 1] << 8) | (buffer[offset]);
}

float
get_float(unsigned char *buffer, size_t offset)
{
        float f;
        unsigned char b[] = { buffer[offset + 0],
                              buffer[offset + 1],
                              buffer[offset + 2],
                              buffer[offset + 3] };
        memcpy(&f, &b, sizeof(f));
        return f;
}

float
get_srational(unsigned char *buffer, size_t offset)
{
        int a = (int)get_int32(buffer, offset);
        int b = (int)get_int32(buffer, offset + 4);
        return (float)a / (float)b;
}

struct MPCameraCalibration
parse_calibration_file(const char *path)
{
        FILE *fp;
        size_t size;
        unsigned char *buffer;

        struct MPCameraCalibration result = { 0 };

        fp = fopen(path, "rb");
        if (fp == NULL) {
                return result;
        }

        fseek(fp, 0, SEEK_END);
        size = ftell(fp);
        fseek(fp, 0, SEEK_SET);
        buffer = malloc(sizeof(char) * size);
        size_t ret = fread(buffer, 1, size, fp);
        if (ret != size) {
                return result;
        }
        fclose(fp);

        if (buffer[0] != 'I' || buffer[1] != 'I') {
                fprintf(stderr, "Magic for DCP file incorrect\n");
                return result;
        }
        if (buffer[2] != 0x52 || buffer[3] != 0x43) {
                fprintf(stderr, "Invalid DCP version\n");
                return result;
        }

        unsigned int ifd0 = get_int32(buffer, 4);
        unsigned int tag_count = get_int16(buffer, ifd0);

        for (int i = 0; i < tag_count; i++) {
                int tag_offset = ifd0 + 2 + (i * 12);
                unsigned int tag = get_int16(buffer, tag_offset + 0);
                unsigned int type = get_int16(buffer, tag_offset + 2);
                unsigned int count = get_int32(buffer, tag_offset + 4);
                unsigned int offset = get_int32(buffer, tag_offset + 8);

                switch (tag) {
                case DCPTAG_COLOR_MATRIX_1:
                        for (int j = 0; j < 9; j++) {
                                float point =
                                        get_srational(buffer, offset + (j * 8));
                                result.color_matrix_1[j] = point;
                        }
                        break;
                case DCPTAG_COLOR_MATRIX_2:
                        for (int j = 0; j < 9; j++) {
                                float point =
                                        get_srational(buffer, offset + (j * 8));
                                result.color_matrix_2[j] = point;
                        }
                        break;
                case DCPTAG_FORWARD_MATRIX_1:
                        for (int j = 0; j < 9; j++) {
                                float point =
                                        get_srational(buffer, offset + (j * 8));
                                result.forward_matrix_1[j] = point;
                        }
                        break;
                case DCPTAG_FORWARD_MATRIX_2:
                        for (int j = 0; j < 9; j++) {
                                float point =
                                        get_srational(buffer, offset + (j * 8));
                                result.forward_matrix_2[j] = point;
                        }
                        break;
                case DCPTAG_CALIBRATION_ILLUMINANT_1:
                        result.illuminant_1 = offset;
                        break;
                case DCPTAG_CALIBRATION_ILLUMINANT_2:
                        result.illuminant_2 = offset;
                        break;
                case DCPTAG_PROFILE_TONE_CURVE:
                        result.tone_curve = malloc(count * sizeof(float));
                        result.tone_curve_length = count;
                        for (int j = 0; j < count; j++) {
                                result.tone_curve[j] =
                                        get_float(buffer, offset + (j * 4));
                        }
                        break;
                case DCPTAG_PROFILE_HUE_SAT_MAP_DIMS:
                        result.hue_sat_map_dims[0] = get_int32(buffer, offset);
                        result.hue_sat_map_dims[1] = get_int32(buffer, offset + 4);
                        result.hue_sat_map_dims[2] = get_int32(buffer, offset + 8);
                        break;
                case DCPTAG_PROFILE_HUE_SAT_MAP_DATA_1:
                        result.hue_sat_map_data_1 = malloc(count * sizeof(float));
                        for (int j = 0; j < count; j++) {
                                result.hue_sat_map_data_1[j] =
                                        get_float(buffer, offset + (j * 4));
                        }
                        break;
                case DCPTAG_PROFILE_HUE_SAT_MAP_DATA_2:
                        result.hue_sat_map_data_2 = malloc(count * sizeof(float));
                        for (int j = 0; j < count; j++) {
                                result.hue_sat_map_data_2[j] =
                                        get_float(buffer, offset + (j * 4));
                        }
                        break;
                }
        }

        return result;
}
