#include <v_lib.h>
#include "eval.h"
#include "cspace.h"
#include "messages.h"
#include "istrings.h"

V_LOCAL vp_stack_t  image_cache = TYPED_STACK_EMPTY_INITIALIZER;

V_LOCAL void
rgbFromCie(vec3_t rgb_ret,vec3_t cie)
{
    static matrix3_t cie2rgb_transform={
        {(vec_t)2.739,(vec_t)-1.145,(vec_t)-0.424},
        {(vec_t)-1.11,(vec_t)2.029,(vec_t)0.033},
        {(vec_t)0.138,(vec_t)-0.333,(vec_t)1.105}};
    mat3VecMul(rgb_ret,cie2rgb_transform,cie);
}

V_LOCAL void
cieFromRgb(vec3_t cie_ret,vec3_t rgb)
{
    static matrix3_t rgb2cie_transform={
        {(vec_t)0.477,(vec_t)0.298,(vec_t)0.174},
        {(vec_t)0.261,(vec_t)0.653,(vec_t)0.081},
        {(vec_t)0.019,(vec_t)0.160,(vec_t)0.907}};
    mat3VecMul(cie_ret,rgb2cie_transform,rgb);
}

V_LOCAL void
ctransCieFromRgb(matrix_p cie[],matrix_p rgb[])
{
    u32_t i, j;
    vec3_t rgbv, ciev;
    for(i = 0;i < 3;i++)
    {
        if(cie[i])
            memFree(cie[i]);
        cie[i] = matAlloc(rgb[0]->rows,rgb[0]->cols);
    }
    for(i = 0;i < rgb[0]->rows;i++)
    {
        for(j = 0;j < rgb[0]->cols;j++)
        {
            rgbv[0] = rgb[0]->cell[i][j];
            rgbv[1] = rgb[1]->cell[i][j];
            rgbv[2] = rgb[2]->cell[i][j];
            cieFromRgb(ciev,rgbv);
            cie[0]->cell[i][j] = ciev[0];
            cie[1]->cell[i][j] = ciev[1];
            cie[2]->cell[i][j] = ciev[2];
        }
    }
}

V_LOCAL void
ctransRgbFromCie(matrix_p rgb[],matrix_p cie[])
{
    u32_t i, j;
    vec3_t rgbv, ciev;
    for(i = 0;i < 3;i++)
    {
        if(rgb[i])
            memFree(rgb[i]);
        rgb[i] = matAlloc(cie[0]->rows,cie[0]->cols);
    }
    for(i = 0;i < cie[0]->rows;i++)
    {
        for(j = 0;j < cie[0]->cols;j++)
        {
            ciev[0] = cie[0]->cell[i][j];
            ciev[1] = cie[1]->cell[i][j];
            ciev[2] = cie[2]->cell[i][j];
            rgbFromCie(rgbv,ciev);
            rgb[0]->cell[i][j] = rgbv[0];
            rgb[1]->cell[i][j] = rgbv[1];
            rgb[2]->cell[i][j] = rgbv[2];
        }
    }
}

V_LOCAL void
ctransHsvFromRgb(matrix_p hsv[],matrix_p rgb[])
{
    u32_t i, j;
    vec3_t rgbv, hsvv;
    for(i = 0;i < 3;i++)
    {
        if(hsv[i])
            memFree(hsv[i]);
        hsv[i] = matAlloc(rgb[0]->rows,rgb[0]->cols);
    }
    for(i = 0;i < rgb[0]->rows;i++)
    {
        for(j = 0;j < rgb[0]->cols;j++)
        {
            rgbv[0] = rgb[0]->cell[i][j];
            rgbv[1] = rgb[1]->cell[i][j];
            rgbv[2] = rgb[2]->cell[i][j];
            hsvFromRgb(hsvv,rgbv);
            hsv[0]->cell[i][j] = hsvv[0];
            hsv[1]->cell[i][j] = hsvv[1];
            hsv[2]->cell[i][j] = hsvv[2];
        }
    }
}

V_LOCAL void
ctransRgbFromHsv(matrix_p rgb[],matrix_p hsv[])
{
    u32_t i, j;
    vec3_t rgbv, hsvv;
    for(i = 0;i < 3;i++)
    {
        if(rgb[i])
            memFree(rgb[i]);
        rgb[i] = matAlloc(hsv[0]->rows,hsv[0]->cols);
    }
    for(i = 0;i < hsv[0]->rows;i++)
    {
        for(j = 0;j < hsv[0]->cols;j++)
        {
            hsvv[0] = hsv[0]->cell[i][j];
            hsvv[1] = hsv[1]->cell[i][j];
            hsvv[2] = hsv[2]->cell[i][j];
            rgbFromHsv(rgbv,hsvv);
            rgb[0]->cell[i][j] = rgbv[0];
            rgb[1]->cell[i][j] = rgbv[1];
            rgb[2]->cell[i][j] = rgbv[2];
        }
    }
}

V_LOCAL cspace_desc_t cspaces[] = 
{
    {
        &ipl_key_rgb,
        CSPACE_RGB_OFFSET,
        3,
        {&ipl_key_red,&ipl_key_green,&ipl_key_blue,NULL},
        &ipl_desc_rgb,
        NULL,
        NULL
    },
    {
        &ipl_key_rgba,
        CSPACE_RGB_OFFSET,
        4,
        {&ipl_key_red,&ipl_key_green,&ipl_key_blue,&ipl_key_alpha},
        &ipl_desc_rgba,
        NULL,
        NULL
    },
    {
        &ipl_key_cie,
        CSPACE_CIE_OFFSET,
        3,
        {&ipl_key_cieX,&ipl_key_cieY,&ipl_key_cieZ,NULL},
        &ipl_desc_cie,
        ctransRgbFromCie,
        ctransCieFromRgb
    },
    {
        &ipl_key_hsv,
        CSPACE_HSV_OFFSET,
        3,
        {&ipl_key_hue,&ipl_key_saturation,&ipl_key_value,NULL},
        &ipl_desc_hsv,
        ctransRgbFromHsv,
        ctransHsvFromRgb
    },
    {NULL,0,0,{NULL,NULL,NULL,NULL},NULL,NULL,NULL}
};

void
cspaceListSpaces()
{
    cspace_desc_p head = cspaces;
    for(;head->key_ptr;head++)
        conPrintf("\t\"%s\": %s%c\n",*head->key_ptr,*head->desc_ptr,
            (head + 1)->key_ptr ? ';' : '.');
}

void
cspaceListChannels()
{
    u32_t i;
    cspace_desc_p head = cspaces;
    for(;head->key_ptr;head++)
    {
        for(i = 0;i < IPL_MAX_COLOR_CHANNELS;i++)
        {
            if(head->channel_names[i])
                conPrintf("\t\"%s\"",*head->channel_names[i]);
        }
        conPuts("\n");
    }
}

V_LOCAL cspace_desc_p
cspaceFindDesc(char *key)
{
    cspace_desc_p head = cspaces;
    for(;head->key_ptr;head++)
        if(STR_EQ(*head->key_ptr,key))
            return head;
    return NULL;
}

V_LOCAL image_input_p
inputFindCachePic(char *image_name)
{
    u32_t i;
    image_input_p pic, *pics;
    pics = (image_input_p*)vpsGetStorage(&image_cache);
    for(i = 0;i < vpsCount(&image_cache);i++)
        if(STR_EQ(pics[i]->id,image_name))
            return pics[i];
    pic = (image_input_p)memAllocZ(sizeof(image_input_t));
    V_ASSERT(pic);
    pic->id = strDup(image_name);
    if(!rgbaLoadChannels(pic->id,
        &pic->spaces[CSPACE_RGB_OFFSET][0],
        &pic->spaces[CSPACE_RGB_OFFSET][1],
        &pic->spaces[CSPACE_RGB_OFFSET][2],
        &pic->spaces[CSPACE_RGB_OFFSET][3]))
    {
        memFree(pic->id);
        memFree(pic);
        return NULL;
    }
    vpsPush(&image_cache,pic);
    return pic;
}

V_LOCAL vboolean
cspaceFindChannel(char *id,cspace_desc_p *desc_ret,u32_t *ch_offset_ret)
{
    u32_t i;
    cspace_desc_p head = cspaces;
    for(;head->key_ptr;head++)
    {
        for(i = 0;i < IPL_MAX_COLOR_CHANNELS;i++)
        {
            if(head->channel_names[i] && STR_EQ(id,*head->channel_names[i]))
            {
                *desc_ret = head;
                *ch_offset_ret = i;
                return true;
            }
        }
    }
    return false;
}

matrix_p
cspaceGetMatrix(char *image_name,char *channel_id)
{
    CALLER_NAME(inputGetMatrix)
    u32_t ch_offset;
    cspace_desc_p cdesc;
    image_input_p pic;
    if(!cspaceFindChannel(channel_id,&cdesc,&ch_offset))
    {
        ERROR_GENERIC_2ARG(ipl_unknown_channel,channel_id);
        return NULL;
    }
    /* it's not necessary to duplicate 'channel_id' */
    pic = inputFindCachePic(image_name);
    if(!pic)
        return NULL;
    if(cdesc->cspace_offset != CSPACE_RGB_OFFSET)
    {
        if(!pic->spaces[cdesc->cspace_offset][ch_offset])
            (*cdesc->native_from_rgba)(pic->spaces[cdesc->cspace_offset],
                pic->spaces[CSPACE_RGB_OFFSET]);
    }
    return pic->spaces[cdesc->cspace_offset][ch_offset];
}

void
cspaceCollectGarbage(void)
{
    u32_t i, j;
    image_input_p ptr;
    while(vpsPop(&image_cache,(void_p*)&ptr))
    {
        for(i = 0;i < IPL_MAX_COLOR_SPACES;i++)
            for(j = 0;j < IPL_MAX_COLOR_CHANNELS;j++)
                if(ptr->spaces[i][j])
                    memFree(ptr->spaces[i][j]);
        memFree(ptr->id);
        memFree(ptr);
    }
    vpsClear(&image_cache);
}

/*
 * evalOutput()
 * Assumes the keyword 'output' already read in.
 */
vboolean
cspaceEvalOutput(vhandle_t script)
{
    CALLER_NAME(evalOutput)
    u32_t i;
    arg_p next_channel;
    char file_name[_MAX_PATH];
    cspace_desc_p cdesc;
    matrix_p *cptr, native[IPL_MAX_COLOR_CHANNELS];
    matrix_p rgba[4] = {NULL,NULL,NULL,NULL};
    if(!lexCopyNextToken(script,file_name,_MAX_PATH))
    {
        SYN_UNEXPECTED_EOF(script);
        return false;
    }
    if(!lexNext(script))
    {
        SYN_UNEXPECTED_EOF(script);
        return false;
    }
    cdesc = cspaceFindDesc(lexLast(script));
    if(!cdesc)
    {
        PARSE_ERROR(script,ipl_wrong_colorspace);
        return false;
    }
    next_channel = evalNextArgument(script,ARG_MATRIX);
    if(!next_channel)
        return false;
    native[0] = next_channel->data.matrix;
    for(i = 1;i < cdesc->components;i++)
    {
        next_channel = evalNextArgument(script,ARG_MATRIX);
        if(!next_channel)
            return false;
        native[i] = next_channel->data.matrix;
        if(!MAT_SAME_SIZE(native[0],native[i]))
        {
            PARSE_ERROR(script,ipl_size_mismatch);
            return false;
        }
    }
    for(;i < IPL_MAX_COLOR_CHANNELS;i++)
        native[i] = NULL;
    if(cdesc->cspace_offset != CSPACE_RGB_OFFSET)
    {
        (*cdesc->rgba_from_native)(rgba,native);
        cptr = rgba;
    }
    else
        cptr = native;
    rgbaSaveChannels(file_name,cptr[0],cptr[1],cptr[2],cptr[3]);
    for(i = 0;i < 4;i++)
    {
        if(rgba[i])
            memFree(rgba[i]);
    }
    return true;
}
