/******************************************************************************
 *
 * Project:  MSG Native Reader
 * Purpose:  Base class for reading in the headers of MSG native images
 * Author:   Frans van den Bergh, fvdbergh@csir.co.za
 *
 ******************************************************************************
 * Copyright (c) 2005, Frans van den Bergh <fvdbergh@csir.co.za>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 ****************************************************************************/

#include "cpl_port.h"
#include "cpl_error.h"

#include "msg_reader_core.h"
#include "msg_basic_types.h"
#include <stdio.h>
#include <string.h>
#include <math.h>

#ifdef DEBUG
#ifdef GDAL_SUPPORT
#undef DEBUG
#endif
#endif

#ifdef GDAL_SUPPORT
#include "cpl_vsi.h"

CPL_CVSID("$Id: msg_reader_core.cpp 36776 2016-12-10 11:17:47Z rouault $");

#else
#define VSIFSeek(fp, pos, ref)    CPL_IGNORE_RET_VAL(fseek(fp, pos, ref))
#define VSIFRead(p, bs, nb, fp)   CPL_IGNORE_RET_VAL(fread(p, bs, nb, fp))
#endif

namespace msg_native_format {

const Blackbody_lut_type Msg_reader_core::Blackbody_LUT[MSG_NUM_CHANNELS+1] = {
    {0,0,0},  // dummy channel
    {0,0,0},  // N/A
    {0,0,0},  // N/A
    {0,0,0},  // N/A
    {2569.094, 0.9959, 3.471},
    {1598.566, 0.9963, 2.219},
    {1362.142, 0.9991, 0.485},
    {1149.083, 0.9996, 0.181},
    {1034.345, 0.9999, 0.060},
    { 930.659, 0.9983, 0.627},
    { 839.661, 0.9988, 0.397},
    { 752.381, 0.9981, 0.576},
    {0,0,0}   // N/A
};

static
void PhDataInit(PH_DATA *data)
{
  data->name[0] = '\0';
  data->value[0] = '\0';
}

static
void SecondaryProdHeaderInit(SECONDARY_PROD_HEADER *header)
{
  PhDataInit(&header->abid);
  PhDataInit(&header->smod);
  PhDataInit(&header->apxs);
  PhDataInit(&header->avpa);
  PhDataInit(&header->lscd);
  PhDataInit(&header->lmap);
  PhDataInit(&header->qdlc);
  PhDataInit(&header->qdlp);
  PhDataInit(&header->qqai);
  PhDataInit(&header->selectedBandIds);
  PhDataInit(&header->southLineSelectedRectangle);
  PhDataInit(&header->northLineSelectedRectangle);
  PhDataInit(&header->eastColumnSelectedRectangle);
  PhDataInit(&header->westColumnSelectedRectangle);
}

Msg_reader_core::Msg_reader_core( const char* fname ) :
    _lines(0),
    _columns(0),
    _line_start(0),
    _col_start(0),
    _col_dir_step(0.0f),
    _line_dir_step(0.0f),
    _f_data_offset(0),
    _f_data_size(0),
    _f_header_offset(0),
    _f_header_size(0),
    _visir_bytes_per_line(0),
    _visir_packet_size(0),
    _hrv_bytes_per_line(0),
    _hrv_packet_size(0),
    _interline_spacing(0),
    _year(0),
    _month(0),
    _day(0),
    _hour(0),
    _minute(0),
    _open_success(false)
{
    memset(&_main_header, 0, sizeof(_main_header));
    SecondaryProdHeaderInit(&_sec_header);
    for (size_t i=0; i < MSG_NUM_CHANNELS; ++i) {
      _calibration[i].cal_slope = 0.0;
      _calibration[i].cal_offset = 0.0;
    }

    FILE* fin = fopen(fname, "rb");
    if( !fin )
    {
        CPLError(CE_Failure, CPLE_OpenFailed,
                 "Could not open file %s", fname);
        return;
    }
    read_metadata_block(fin);
    fclose(fin);
}

Msg_reader_core::Msg_reader_core( FILE* fp ) :
    _lines(0),
    _columns(0),
    _line_start(0),
    _col_start(0),
    _col_dir_step(0.0f),
    _line_dir_step(0.0f),
    _f_data_offset(0),
    _f_data_size(0),
    _f_header_offset(0),
    _f_header_size(0),
    _visir_bytes_per_line(0),
    _visir_packet_size(0),
    _hrv_bytes_per_line(0),
    _hrv_packet_size(0),
    _interline_spacing(0),
    _year(0),
    _month(0),
    _day(0),
    _hour(0),
    _minute(0),
    _open_success(false)
{
    memset(&_main_header, 0, sizeof(_main_header));

    SecondaryProdHeaderInit(&_sec_header);
    for (size_t i=0; i < MSG_NUM_CHANNELS; ++i) {
      _calibration[i].cal_slope = 0.0;
      _calibration[i].cal_offset = 0.0;
    }

    read_metadata_block(fp);
}

void Msg_reader_core::read_metadata_block(FILE* fin) {
    _open_success = true;

    unsigned int i;

    CPL_IGNORE_RET_VAL(VSIFRead(&_main_header, sizeof(_main_header), 1, fin));
    CPL_IGNORE_RET_VAL(VSIFRead(&_sec_header, sizeof(_sec_header), 1, fin));

#ifdef DEBUG
    // print out all the fields in the header
    PH_DATA* hd = (PH_DATA*)&_main_header;
    for (int i=0; i < 6; i++) {
        to_string(*hd);
        printf("[%02d] %s %s", i, hd->name, hd->value);/*ok*/
        hd++;
    }
    PH_DATA_ID* hdi = (PH_DATA_ID*)&_main_header.dataSetIdentification;

    for (i=0; i < 5; i++) {
        printf("%s %s %s", hdi->name, hdi->size, hdi->address);/*ok*/
        hdi++;
    }
    hd = (PH_DATA*)(&_main_header.totalFileSize);
    for (int i=0; i < 19; i++) {
        to_string(*hd);
        printf("[%02d] %s %s", i, hd->name, hd->value);/*ok*/
        hd++;
    }
#endif // DEBUG

    // extract data & header positions

    for (i=0; i < 5; i++) {
        PH_DATA_ID* hdi = (PH_DATA_ID*)&_main_header.dataSetIdentification[i];
        if (STARTS_WITH(hdi->name, "15Header")) {
            sscanf(hdi->size, "%u", &_f_header_size);
            sscanf(hdi->address, "%u", &_f_header_offset);
        } else
            if (STARTS_WITH(hdi->name, "15Data")) {
            sscanf(hdi->size, "%u", &_f_data_size);
            sscanf(hdi->address, "%u", &_f_data_offset);
        }
    }
#ifdef DEBUG
    printf("Data: %u %u\n", _f_data_offset, _f_data_size);/*ok*/
    printf("Header: %u %u\n", _f_header_offset, _f_header_size);/*ok*/
#endif // DEBUG

    unsigned int lines;
    sscanf(_sec_header.northLineSelectedRectangle.value, "%u", &_lines);
    sscanf(_sec_header.southLineSelectedRectangle.value, "%u", &lines);
    _line_start = lines;
    _lines -= lines - 1;

    unsigned int cols;
    sscanf(_sec_header.westColumnSelectedRectangle.value, "%u", &_columns);
    sscanf(_sec_header.eastColumnSelectedRectangle.value, "%u", &cols);
    _col_start = cols;
    _columns -= cols - 1;

#ifdef DEBUG
    printf("lines = %u, cols = %u\n", _lines, _columns);/*ok*/
#endif // DEBUG

    int records_per_line = 0;
    for (i=0; i < MSG_NUM_CHANNELS; i++) {
        if (_sec_header.selectedBandIds.value[i] == 'X') {
            _bands[i] = 1;
            records_per_line += (i == (MSG_NUM_CHANNELS-1)) ? 3 : 1;
        } else {
            _bands[i] = 0;
        }
    }

#ifdef DEBUG
    printf("reading a total of %d records per line\n", records_per_line);/*ok*/
#endif // DEBUG

    // extract time fields, assume that SNIT is the correct field:
    sscanf(_main_header.snit.value +  0, "%04u", &_year);
    sscanf(_main_header.snit.value +  4, "%02u", &_month);
    sscanf(_main_header.snit.value +  6, "%02u", &_day);
    sscanf(_main_header.snit.value +  8, "%02u", &_hour);
    sscanf(_main_header.snit.value + 10, "%02u", &_minute);

    // read radiometric block
    RADIOMETRIC_PROCESSING_RECORD rad;
    off_t offset = RADIOMETRICPROCESSING_RECORD_OFFSET + _f_header_offset + sizeof(GP_PK_HEADER) + sizeof(GP_PK_SH1) + 1;
    CPL_IGNORE_RET_VAL(VSIFSeek(fin, offset, SEEK_SET));
    CPL_IGNORE_RET_VAL(VSIFRead(&rad, sizeof(RADIOMETRIC_PROCESSING_RECORD), 1, fin));
    to_native(rad);
    memcpy((void*)_calibration, (void*)&rad.level1_5ImageCalibration,sizeof(_calibration));

#ifdef DEBUG
    for (unsigned int i=0; i < MSG_NUM_CHANNELS; i++) {
        if (_calibration[i].cal_slope < 0 || _calibration[i].cal_slope > 0.4)
        {
            printf("Warning: calibration slope (%f) out of nominal range. MSG reader probably broken\n", _calibration[i].cal_slope);/*ok*/
        }
        if (_calibration[i].cal_offset > 0 || _calibration[i].cal_offset < -20) {
            printf("Warning: calibration offset (%f) out of nominal range. MSG reader probably broken\n",/*ok*/ _calibration[i].cal_offset);
        }
    }
#endif

    // read image description block
    IMAGE_DESCRIPTION_RECORD idr;
    offset = RADIOMETRICPROCESSING_RECORD_OFFSET  - IMAGEDESCRIPTION_RECORD_LENGTH + _f_header_offset + sizeof(GP_PK_HEADER) + sizeof(GP_PK_SH1) + 1;
    CPL_IGNORE_RET_VAL(VSIFSeek(fin, offset, SEEK_SET));
    CPL_IGNORE_RET_VAL(VSIFRead(&idr, sizeof(IMAGE_DESCRIPTION_RECORD), 1, fin));
    to_native(idr);
    _line_dir_step = idr.referencegrid_visir.lineDirGridStep;
    _col_dir_step = idr.referencegrid_visir.columnDirGridStep;

    // Rather convoluted, but this code is required to compute the real data block sizes
    // It does this by reading in the first line of every band, to get to the packet size field
    GP_PK_HEADER gp_header;
    GP_PK_SH1    sub_header;
    SUB_VISIRLINE visir_line;

    CPL_IGNORE_RET_VAL(VSIFSeek(fin, _f_data_offset, SEEK_SET));

    _hrv_packet_size = 0;
    _interline_spacing = 0;
    visir_line.channelId = 0;

    int scanned_bands[MSG_NUM_CHANNELS];
    int band_count = 0;
    for (i=0; i < MSG_NUM_CHANNELS; i++) {
        scanned_bands[i] = _bands[i];
        band_count += _bands[i];
    }

    do {
        CPL_IGNORE_RET_VAL(VSIFRead(&gp_header, sizeof(GP_PK_HEADER), 1, fin));
        CPL_IGNORE_RET_VAL(VSIFRead(&sub_header, sizeof(GP_PK_SH1), 1, fin));
        CPL_IGNORE_RET_VAL(VSIFRead(&visir_line, sizeof(SUB_VISIRLINE), 1, fin));
        to_native(visir_line);
        to_native(gp_header);

        // skip over the actual line data
        CPL_IGNORE_RET_VAL(VSIFSeek(fin,
            gp_header.packetLength - (sizeof(GP_PK_SH1) + sizeof(SUB_VISIRLINE) - 1),
            SEEK_CUR
        ));

        if (visir_line.channelId == 0 || visir_line.channelId > MSG_NUM_CHANNELS) {
            _open_success = false;
            break;
        }

        if (scanned_bands[visir_line.channelId - 1]) {
            scanned_bands[visir_line.channelId - 1] = 0;
            band_count--;

            if (visir_line.channelId != 12) { // not the HRV channel
                _visir_bytes_per_line = gp_header.packetLength - (unsigned int)(sizeof(GP_PK_SH1) + sizeof(SUB_VISIRLINE) - 1);
                _visir_packet_size = gp_header.packetLength + (unsigned int)sizeof(GP_PK_HEADER) + 1;
                _interline_spacing += _visir_packet_size;
            } else {
                _hrv_bytes_per_line = gp_header.packetLength - (unsigned int)(sizeof(GP_PK_SH1) + sizeof(SUB_VISIRLINE) - 1);
                _hrv_packet_size = gp_header.packetLength + (unsigned int)sizeof(GP_PK_HEADER) + 1;
                _interline_spacing +=  3*_hrv_packet_size;
                CPL_IGNORE_RET_VAL(VSIFSeek(fin, 2*gp_header.packetLength, SEEK_CUR ));
            }
        }
    } while (band_count > 0);
}

#ifndef GDAL_SUPPORT

int Msg_reader_core::_chan_to_idx(Msg_channel_names channel) {
    unsigned int idx = 0;
    while (idx < MSG_NUM_CHANNELS) {
        if ( (1 << (idx+1)) == (int)channel ) {
            return idx;
        }
        idx++;
    }
    return 0;
}

void Msg_reader_core::get_pixel_geo_coordinates(unsigned int line, unsigned int column, double& longitude, double& latitude) {
    Conversions::convert_pixel_to_geo((unsigned int)(line + _line_start), (unsigned int)(column + _col_start), longitude, latitude);
}

void Msg_reader_core::get_pixel_geo_coordinates(double line, double column, double& longitude, double& latitude) {
    Conversions::convert_pixel_to_geo(line + _line_start, column + _col_start, longitude, latitude);
}

double Msg_reader_core::compute_pixel_area_sqkm(double line, double column) {
    return Conversions::compute_pixel_area_sqkm(line + _line_start, column + _col_start);
}

#endif // GDAL_SUPPORT

} // namespace msg_native_format
