//
// C++ Implementation: %{MODULE}
//
// Description:
//
//
// Author: %{AUTHOR} <%{EMAIL}>, (C) %{YEAR}
//
// Copyright: See COPYING file that comes with this distribution
//
//
#include <stdio.h>
#include <string.h>
#include "merisfiledata.h"
#include "epr_record.h"
#include "epr_band.h"
#include "epr_field.h"
#include "epr_core.h"
#include <sstream>
#include <cfloat>

MerisFileData::MerisFileData(const string &filename, const string &mode)
:FileData(filename,mode),FileDataReader(filename,mode)
{

    this->filename=filename;

    if (!exists_file(filename)) {
        bad_file e(filename);
        throw e;
    }

    /* Initialize the API. Set log-level to DEBUG and use default log-output (stdout) */
    epr_init_api(e_log_warning, epr_log_message, NULL);
    /* Open the product; an argument is a path to product data file */
    product_id = epr_open_product(filename.c_str());

    const EPR_SField* field_start = epr_get_field(epr_get_mph(product_id), "SENSING_START");
    const EPR_SField* field_stop  = epr_get_field(epr_get_mph(product_id), "SENSING_STOP");

    string start = epr_get_field_elem_as_str(field_start);
    string stop  = epr_get_field_elem_as_str(field_stop);

    string sdate;

    Date bdate;

    if(start.substr(0,1)=="\"") {
        start = start.substr(1,20);
    }

    sdate=start.substr(7,4)+getMonth(start.substr(3,3))+start.substr(0,2)+start.substr(12,2)+start.substr(15,2)+start.substr(18,2);

    bdate.set_date_str(sdate,(const char *) "%Y%m%d%H%M%S");

    Date edate;

    if(stop.substr(0,1)=="\"") {
        stop = stop.substr(1,20);
    }

    sdate=stop.substr(7,4)+getMonth(stop.substr(3,3))+stop.substr(0,2)+stop.substr(12,2)+stop.substr(15,2)+stop.substr(18,2);

    edate.set_date_str(sdate,(const char *)   "%Y%m%d%H%M%S");

    this->set_date(bdate);
    this->time_coverage=edate.get_epoch_time()-bdate.get_epoch_time();

}

MerisFileData::~MerisFileData()
{
    /* Close product_id and release rest of the allocated memory */
    epr_close_product(product_id);
    /* Closes product reader API, release all allocated resources */
    epr_close_api();
    product_id=NULL;
}

void MerisFileData::open_data_file()
{
}

void MerisFileData::close_data_file()
{
}


void* MerisFileData::read_data(void* data,
                            const char* sds_name,
                            int * start,
                            int * stride,
                            int *edges,
                            int rank)
{
    EPR_SBandId* band_id = NULL;
    int err_code;
    EPR_SRaster* raster = NULL;
    uint source_w, source_h;
    uint source_step_x, source_step_y;
    uint start_step_x, start_step_y;


    band_id = epr_get_band_id(product_id, sds_name);
//     if (band_id == NULL) {
//         printf("Error: band '%s' not found\n", band_name);
//         return 1;
//     }
    if(start==NULL){
        start_step_x = 0;
        start_step_y = 0;
    }else{
        start_step_x = start[0];
        start_step_y = start[1];
    }

    if(edges==NULL){
        source_w = epr_get_scene_width(product_id);
        source_h = epr_get_scene_height(product_id);
    }else{
        source_w = edges[0];
        source_h = edges[1];
    }

    if(stride == NULL) {
        source_step_x = 1;
        source_step_y = 1;
    }else{
        source_step_x = stride[0];
        source_step_y = stride[1];
    }

    raster = epr_create_compatible_raster(band_id, source_w, source_h, source_step_x, source_step_y);

    EPR_EDataTypeId band_id_data_type = band_id->data_type;
    EPR_ESampleModel band_smod = band_id->sample_model;

    if(sds_name!=string("latitude") && sds_name!=string("longitude") && sds_name!=string("atm_press") ){
        if(band_id->sample_model != e_smod_3TOI && band_id->sample_model != e_smod_2TOF){
            EPR_SDatasetId* dataset_id = band_id->dataset_ref.dataset_id;
            EPR_SRecord* record = epr_create_record(dataset_id);
            EPR_SFieldInfo* field_info = (EPR_SFieldInfo*)epr_get_ptr_array_elem_at(record->info->field_infos, band_id->dataset_ref.field_index - 1);
            raster->data_type = field_info->data_type_id;
            band_id->data_type = field_info->data_type_id;
        }
    }


    err_code = epr_read_band_raster(band_id, start_step_x, start_step_y, raster);

    band_id->data_type = band_id_data_type;
    band_id->sample_model = band_smod;

    if(data==NULL) {
        data = raster->buffer;
   }else{
       int size = 0;
       switch (get_dataset_data_type(sds_name)) {
        case DFNT_UINT8:{
            size=1;
            break;
        }
        case DFNT_INT8:{
            size=1;
            break;
        }
        case DFNT_UINT16:{
            size=2;
            break;
        }
        case DFNT_INT16:{
            size=2;
            break;
        }
        case DFNT_UINT32:{
            size=4;
            break;
        }
        case DFNT_INT32:{
            size=4;
            break;
        }
        case DFNT_FLOAT32:{
            size=4;
            break;
        }
        case DFNT_FLOAT64:{
            size=8;
            break;
        }
        default:{
            bad_file e("bad type data");
            throw e;
            break;
        }
        }
        memcpy (data,raster->buffer,size*(raster->raster_width)*(raster->raster_height));
        free(raster->buffer);
   }

    return data;
}

vector<int> MerisFileData::get_dataset_dimension(const string &sds_name)
{
    vector<int> v;

    int source_w = epr_get_scene_width(product_id);
    int source_h = epr_get_scene_height(product_id);

    v.push_back(source_h);
    v.push_back(source_w);

    return v;
}


void MerisFileData::get_dataset_fill_value(const string &sds_name, void* fill_value)
{
    switch(get_dataset_data_type(sds_name)){
        case DFNT_UCHAR8:{
            uchar8 x;
            getFillValues(x);
            uchar8 **y;
            y = (uchar8**)&fill_value;
            (**y) = x;
            break;
        }
        case DFNT_CHAR8:{
            char8 x;
            getFillValues(x);
            char8 **y;
            y = (char8**)&fill_value;
            (**y) = x;
            break;
        }
        case DFNT_UINT16:{
            uint16 x;
            getFillValues(x);
            uint16 **y;
            y = (uint16**)&fill_value;
            (**y) = x;
            break;
        }
        case DFNT_INT16:{
            int16 x;
            getFillValues(x);
            int16 **y;
            y = (int16**)&fill_value;
            (**y) = x;
            break;
        }
        case DFNT_UINT32:{
            uint32 x;
            getFillValues(x);
            uint32 **y;
            y = (uint32**)&fill_value;
            (**y) = x;
            break;
        }
        case DFNT_INT32:{
            int32 x;
            getFillValues(x);
            int32 **y;
            y = (int32**)&fill_value;
            (**y) = x;
            break;
        }
        case DFNT_FLOAT32:{
            float32 x;
            getFillValues(x);
            float32 **y;
            y = (float32**)&fill_value;
            (**y) = x;
            break;
        }
        case DFNT_FLOAT64:{
            float64 x;
            getFillValues(x);
            float64 **y;
            y = (float64**)&fill_value;
            (**y) = x;
            break;
        }
    }
}

int MerisFileData::get_n_dataset()
{
    return epr_get_num_bands(product_id);
};

string MerisFileData::get_dataset_name(int i)
{
    EPR_SBandId* band_id = epr_get_band_id_at(product_id, i);
    return epr_get_band_name(band_id);
};

int MerisFileData::get_dataset_data_type(string sds_name)
{
    EPR_SBandId* band_id = epr_get_band_id(product_id, sds_name.c_str());
    EPR_EDataTypeId datatype_id;

    if(sds_name!="latitude" && sds_name!="longitude" && sds_name!=string("atm_press") && band_id->sample_model != e_smod_3TOI && band_id->sample_model != e_smod_2TOF){
        EPR_SDatasetId* dataset_id = band_id->dataset_ref.dataset_id;
        EPR_SRecord* record = epr_create_record(dataset_id);
        EPR_SFieldInfo* field_info = (EPR_SFieldInfo*)epr_get_ptr_array_elem_at(record->info->field_infos, band_id->dataset_ref.field_index - 1);
        datatype_id = field_info->data_type_id;
    }  else{
        datatype_id = band_id->data_type;
    }

    switch (datatype_id) {
        case e_tid_string:
        case e_tid_time:
        case e_tid_spare:{
            bad_file e("bad type data");
            throw e;
            break;
        }
        case e_tid_uchar:{
            return DFNT_UINT8;
        }
        case e_tid_char:{
            return DFNT_INT8;
        }
        case e_tid_ushort:{
            return DFNT_UINT16;
        }
        case e_tid_short:{
            return DFNT_INT16;
        }
        case e_tid_ulong:{
            return DFNT_UINT32;
        }
        case e_tid_long:{
            return DFNT_INT32;
        }
        case e_tid_float:{
            return DFNT_FLOAT32;
        }
        case e_tid_double:{
            return DFNT_FLOAT64;
        }
        default:{
            bad_file e("bad type data");
            throw e;
            break;
        }
    }
}

string MerisFileData::get_values_attr_dataset(string sds_name,string attr_name)
{
    EPR_SDatasetId* dataset_id = epr_get_band_id(product_id, sds_name.c_str())->dataset_ref.dataset_id;
    const EPR_SRecord* record = epr_create_record_from_info(epr_get_record_info(dataset_id));
    const EPR_SField* field = epr_get_field(record,attr_name.c_str());
    if(field==NULL) return "";
    string str=epr_get_field_elem_as_str(field);
    return str.c_str();
}

bool MerisFileData::has_attr_dataset(string sds_name,string attr_name)
{
    EPR_SDatasetId* dataset_id = epr_get_band_id(product_id, sds_name.c_str())->dataset_ref.dataset_id;
    const EPR_SRecord* record = epr_create_record_from_info(epr_get_record_info(dataset_id));
    const char *field_name = attr_name.c_str();
    EPR_SField* field;

    for (uint field_index = 0; field_index < record->num_fields; field_index++)  {
        field = record->fields[field_index];
        if (strcmp(field_name, field->info->name) == 0) {
            return true;
        }
    }

    return false;
}

string MerisFileData::get_values_attr(string attr_name)
{
    const EPR_SRecord* record_mph = epr_get_mph(product_id);
    const EPR_SRecord* record_sph = epr_get_sph(product_id);
    const EPR_SField* field = NULL;

    for (uint field_index = 0; field_index < record_mph->num_fields; field_index++)  {
        field = record_mph->fields[field_index];
        const char *field_name = attr_name.c_str();
        if (strcmp(field_name, field->info->name) == 0) {
            field = epr_get_field(record_mph,attr_name.c_str());
            string str=epr_get_field_elem_as_str(field);
            return str.c_str();
        }
    }

    field = epr_get_field(record_sph,attr_name.c_str());
    if(field==NULL) return "";
    string str=epr_get_field_elem_as_str(field);
    return str.c_str();
}

bool MerisFileData::has_attr(string attr_name)
{
    const EPR_SRecord* record_mph = epr_get_mph(product_id);
    const EPR_SRecord* record_sph = epr_get_sph(product_id);
    const char *field_name = attr_name.c_str();
    EPR_SField* field;

    for (uint field_index = 0; field_index < record_mph->num_fields; field_index++)  {
        field = record_mph->fields[field_index];
        if (strcmp(field_name, field->info->name) == 0) {
            return true;
        }
    }

    for (uint field_index = 0; field_index < record_sph->num_fields; field_index++)  {
        field = record_sph->fields[field_index];
        if (strcmp(field_name, field->info->name) == 0) {
            return true;
        }
    }

    return false;
}

string MerisFileData::getMonth(const string &month)
{
    if(month=="JAN") return "01";
    else if(month=="FEB") return "02";
    else if(month=="MAR") return "03";
    else if(month=="APR") return "04";
    else if(month=="MAY") return "05";
    else if(month=="JUN") return "06";
    else if(month=="JUL") return "07";
    else if(month=="AUG") return "08";
    else if(month=="SEP") return "09";
    else if(month=="OCT") return "10";
    else if(month=="NOV") return "11";
    else if(month=="DEC") return "12";
    bad_file e(month);
    throw e;
}

void MerisFileData::get_fillValue(const string &sds_name, void *fillValue) {
    if(sds_name=="surf_press") {
        uint8 *fv = (uint8*)fillValue;
        fv=0;
        return;
    }
    get_dataset_fill_value(sds_name, (void*) &fillValue);
}

void MerisFileData::getFillValues(char8 &fillvalue) {
    fillvalue = static_cast<int8 >((int8 )SCHAR_MIN);
}

void MerisFileData::getFillValues(uchar8 &fillvalue) {
    fillvalue = static_cast<uint8 >((uint8 )UCHAR_MAX);
}

void MerisFileData::getFillValues(int16 &fillvalue) {
    fillvalue = static_cast<int16 >((int16 )SHRT_MIN);
}

void MerisFileData::getFillValues(uint16 &fillvalue) {
    fillvalue = static_cast<uint16 >((uint16 )USHRT_MAX);
}

void MerisFileData::getFillValues(int32 &fillvalue) {
    fillvalue = static_cast<int32 >((int32 )LONG_MIN);
}

void MerisFileData::getFillValues(uint32 &fillvalue) {
    fillvalue = static_cast<uint32 >((uint32 )ULONG_MAX);
}

void MerisFileData::getFillValues(float32 &fillvalue) {
    fillvalue = static_cast<float32 >((float32 )-DBL_MAX);
}

void MerisFileData::getFillValues(float64 &fillvalue) {
    fillvalue = static_cast<float64 >((float64 )-LDBL_MAX);
}


/**
 * Interprates a memory data as a string.
 *
 * @param field the pointer at the array to convert
 *
 * @return the <code>char</code> typed element
 *         or <code>NULL</code> if an error occured.
 */
string MerisFileData::epr_get_field_elem_as_str(const EPR_SField* field)
{
    char *str = new char[field->info->num_elems * 128];

    epr_clear_err();

    if (field == NULL) {
        epr_set_err(e_err_invalid_field_name,
                    "epr_get_field_elem_as_str: invalid field name");
        return NULL;
    }

    if (field->info->data_type_id == e_tid_string)
    {
        sprintf(str, "\"%s\"", (const char*) field->elems);
/*
        sprintf(ostream, "\"");
        for (i = 0; i < field->info->num_elems; i++)
        {
            sprintf(ostream, "%c", ((char*) field->elems)[i]);
        }
        sprintf(ostream, "\"");
*/
    }
    else if (field->info->data_type_id == e_tid_time)
    {
        EPR_STime* time = (EPR_STime*) field->elems;
        sprintf(str, "{d=%ld, j=%ld, m=%ld}", time->days, time->seconds, time->microseconds);
    }
    else {
        if (field->info->num_elems > 1) {
            sprintf(str, "{");
        }
        for (uint i = 0; i < field->info->num_elems; i++)
        {
            if (i > 0)
                sprintf(str, ", ");
            switch (field->info->data_type_id)
            {
            case e_tid_uchar:
                sprintf(str, "%u", ((uchar*) field->elems)[i]);
                break;
            case e_tid_char:
                sprintf(str, "%d", ((char*) field->elems)[i]);
                break;
            case e_tid_ushort:
                sprintf(str, "%u", ((ushort*) field->elems)[i]);
                break;
            case e_tid_short:
                sprintf(str, "%d", ((short*) field->elems)[i]);
                break;
            case e_tid_ulong:
                sprintf(str, "%lu", ((ulong*) field->elems)[i]);
                break;
            case e_tid_long:
                sprintf(str, "%d", ((int*) field->elems)[i]);
                break;
            case e_tid_float:
                sprintf(str, "%f", ((float*) field->elems)[i]);
                break;
            case e_tid_double:
                sprintf(str, "%f", ((double*) field->elems)[i]);
                break;
            default:
                sprintf(str, "<<unknown data type>>");
            }
        }
        if (field->info->num_elems > 1) {
            sprintf(str, "}");
        }
    }

    return str;
}


void MerisFileData::get_scaling(const string &sds_name, float64 &scale, float64 &offset)
{
    if(sds_name==string("latitude") || sds_name==string("longitude") || sds_name==string("atm_press") ){
        scale = 1.0;
        offset = 0.0;
    }else{
        EPR_SBandId *band_id=epr_get_band_id(product_id, sds_name.c_str());
        scale = band_id->scaling_factor;
        offset = band_id->scaling_offset;
    }
};