/* 
 * File:   ImageDecoder.cpp
 * Author: yuri
 * 
 * Created on June 13, 2011, 1:32 PM
 */

#include <vector>
#include "./include/io.hpp"
#include "./include/ImageDecoder.hpp"
#include "./include/messages.h"
#include "./include/DataManager.hpp"
#include "lzma/LzmaLib.h"
#include "fileio/fileio.hpp"
#include <assert.h>
#include <fstream>


using namespace std;

ImageDecoder::ImageDecoder() {
    image = new image_t();
    for(int i=0; i< 256; i++){
        path_t *p = new path_t();
        image->push_back(p);
    }
    width=-1;
    height=-1;
    

}

ImageDecoder::ImageDecoder(const ImageDecoder& orig) {
}

int MAXR =0;

ImageDecoder::~ImageDecoder() {
}


/* Add a point to the current datatype. This is used for both starting points, and neighbouring points.
 * Identify a starting point by setting "point" to 0. This cannot happen for neighbouring points, as that
 * implicates the radius is 0.*/
void ImageDecoder::addPoint(char point, int &x, int &y, int &r, path_t *path){
    
    /* If we are a neighbouring point: decode the character and walk in the direction. Modify the original
     * x,y,r values. */
    if(point != 0){
        unsigned char pos = point >> 4;
        signed char dr = (point & 15) - 8;
    
        switch(pos){
            case 0: ++x; ++y; break;
            case 1:      ++y; break;
            case 2: --x; ++y; break;
            case 3: ++x;      break;
            case 4: --x;      break;
            case 5: ++x; --y; break;
            case 6:      --y; break;
            case 7: --x; --y; break;
            default:
                PRINT(MSG_ERROR, "Neighbouring encoding failed. Decoded value was NOT a neighbour.\n");
                exit(1);
        }
        r += dr;
    }
    
    if(r<0){ cerr << "Invalid radius size, must be >0 [r=" << r << "]" << endl; }
    
    /* Add to datatype */
    path->push_back(coord3D_t(x,y,r));

}

/* Decode LZMA. */
int ImageDecoder::decodeLZMA(unsigned char *compr_data, int length, unsigned char **out, unsigned int *outlength){
    unsigned char *dat_it = compr_data;
    unsigned char *decompr_data;
    size_t decompr_length=-1;
    SizeT compr_length=length;

    /* First 4 bytes store the size of the uncompressed data. */
    decompr_length = *((unsigned int *) dat_it);
    *outlength = decompr_length;
    decompr_data = new unsigned char[*outlength];
    
    dat_it += 4; compr_length -= 4;

    /* Remove the preceeding LZMA properties */
    compr_length -= LZMA_PROPS_SIZE;

    PRINT(MSG_NORMAL, "LZMA Input size: %f KB, Uncompressed output: %fKB\n", length/1024.0, *outlength/1024.0);
     int res = LzmaUncompress( decompr_data, &decompr_length,
                    &dat_it[LZMA_PROPS_SIZE], &compr_length,
                    &dat_it[0], LZMA_PROPS_SIZE);
    switch(res){
        case SZ_OK:
            cout << "LZMA Decompression went OK." << endl;
            break;
        case SZ_ERROR_DATA:
            cout << "Data error." << endl;
            break;
        case SZ_ERROR_MEM:
            cout << "Memory Allocation Error." << endl;
            break;
         case SZ_ERROR_UNSUPPORTED:
            cout << "Unsupported Properties" << endl;
            break;
        case SZ_ERROR_INPUT_EOF:
            cout << "Needs more bytes in input buffer!!" << endl;
            break;
        default:
            cout << "LZMA returned unknown return value" << endl;
    }

    /* Fill return values */
    *out = decompr_data;
    *outlength = decompr_length;
            
    free (compr_data);
    return res;
}

/* Load SIR file, decode LZMA, and read all disks. */
bool ImageDecoder::load(const char *fname){
    unsigned char *data=0, *dat_it;
    int intensity, numPaths;
    int x,y,r;
    uint8_t current;
    path_t *path;

    unsigned int nEl = readFile<unsigned char>(fname, &data);
    if(nEl == 0){
        PRINT(MSG_ERROR, "Could not open file for Image Decoder.\n");
        exit(1);
    }

    /* Decode LZMA data, and overwrite old data with the decompressed data*/
    decodeLZMA(data,nEl, &data, &nEl);


    /* Initialize iterator */
    dat_it=data;

    int version = READUINT16(dat_it);
    if(version != READER_FILE_VERSION_NUMBER){
        cerr << "Incorrect version. Can read: " <<  READER_FILE_VERSION_NUMBER << " , encountered: " <<  version << endl;
    }


    /* Get Width and Height */
    width =  READUINT16(dat_it);
    height = READUINT16(dat_it);
    //dat_it += 4;

    while(dat_it < data + nEl){

        intensity = READUINT8(dat_it);
        numPaths =  READUINT16(dat_it);
        path_t *layer = (*image)[intensity];

        for(int i=0; i<numPaths; ++i){
            path = new path_t();
            
            /* Read first -full- point */
            x = READUINT16(dat_it);
            y = READUINT16(dat_it);
            r = READUINT16(dat_it);
            path->push_back(coord3D_t(x,y,r));

            bool end = false;
            while(!end){
                current = READUINT8(dat_it);
                /* End of object?*/
                if(current == END_TAG){
                    end = true;
                }

                /* End of branch? */
                else if(current == FORK_TAG){
                    uint16_t goBack = READUINT16(dat_it);
                    for(unsigned int i=0; i<goBack; ++i){
                        layer->push_back((path->back()));
                        path->pop_back();

                    }

                    x= path->back().first;
                    y= path->back().second;
                    r= path->back().third;
                }

                else{
                    addPoint(current, x,y,r, path);
                }

            }


            /* Copy path to the layer of the image */
            for(unsigned int i=0; i<path->size(); ++i){
                layer->push_back((*path)[i]);
            }
            delete path;
        }

    }

    delete data;
    return true;

}

