/******************************************************************************
 *
 * Purpose:  Implementation of the CPCIDSKChannel Abstract class.
 *
 ******************************************************************************
 * Copyright (c) 2009
 * PCI Geomatics, 90 Allstate Parkway, Markham, Ontario, Canada.
 *
 * SPDX-License-Identifier: MIT
 ****************************************************************************/

#include "pcidsk_config.h"
#include "pcidsk_types.h"
#include "core/pcidsk_utils.h"
#include "pcidsk_exception.h"
#include "pcidsk_channel.h"
#include "core/cpcidskfile.h"
#include "channel/cpcidskchannel.h"
#include "channel/ctiledchannel.h"
#include <cstring>
#include <cassert>
#include <cstdlib>
#include <cstring>
#include <cstdio>

using namespace PCIDSK;

PCIDSKChannel::~PCIDSKChannel() = default;

/************************************************************************/
/*                           CPCIDSKChannel()                           */
/************************************************************************/

CPCIDSKChannel::CPCIDSKChannel( PCIDSKBuffer &image_header,
                                uint64 ih_offsetIn,
                                CPCIDSKFile *fileIn,
                                eChanType pixel_typeIn,
                                int channel_numberIn )

{
    this->pixel_type = pixel_typeIn;
    this->file = fileIn;
    this->channel_number = channel_numberIn;
    this->ih_offset = ih_offsetIn;
    is_locked = false;
    byte_order = 'N';
    needs_swap = !BigEndianSystem();

    width = file->GetWidth();
    height = file->GetHeight();

    block_width = width;
    block_height = 1;

/* -------------------------------------------------------------------- */
/*      Establish if we need to byte swap the data on load/store.       */
/* -------------------------------------------------------------------- */
    if( channel_number != -1 )
    {
        unsigned short test_value = 1;

        is_locked = image_header.buffer[200] == 'W';
        byte_order = image_header.buffer[201];
        if( (reinterpret_cast<uint8 *>(&test_value))[0] == 1 )
            needs_swap = (byte_order != 'S');
        else
            needs_swap = (byte_order == 'S');

        if( pixel_type == CHN_8U )
            needs_swap = 0;

        LoadHistory( image_header );

/* -------------------------------------------------------------------- */
/*      Initialize the metadata object, but do not try to load till     */
/*      needed.  We avoid doing this for unassociated channels such     */
/*      as overviews.                                                   */
/* -------------------------------------------------------------------- */
        metadata.Initialize( file, "IMG", channel_number );
    }

/* -------------------------------------------------------------------- */
/*      No overviews for unassociated files, so just mark them as       */
/*      initialized.                                                    */
/* -------------------------------------------------------------------- */
    overviews_initialized = (channel_number == -1);
}

/************************************************************************/
/*                          ~CPCIDSKChannel()                           */
/************************************************************************/

CPCIDSKChannel::~CPCIDSKChannel()

{
    InvalidateOverviewInfo();
}

/************************************************************************/
/*                       InvalidateOverviewInfo()                       */
/*                                                                      */
/*      This is called when CreateOverviews() creates overviews - we    */
/*      invalidate our loaded info and re-establish on a next request.  */
/************************************************************************/

void CPCIDSKChannel::InvalidateOverviewInfo()

{
    for( size_t io=0; io < overview_bands.size(); io++ )
    {
        if( overview_bands[io] != nullptr )
        {
            delete overview_bands[io];
            overview_bands[io] = nullptr;
        }
    }

    overview_infos.clear();
    overview_bands.clear();
    overview_decimations.clear();

    overviews_initialized = false;
}

/************************************************************************/
/*                       EstablishOverviewInfo()                        */
/************************************************************************/
void CPCIDSKChannel::EstablishOverviewInfo() const

{
    if( overviews_initialized )
        return;

    overviews_initialized = true;

    std::vector<std::string> keys = GetMetadataKeys();
    if( !keys.empty() )
    {
        std::sort(keys.begin(), keys.end(),
              [] (const std::string &first,
                             const std::string &second)
                {
                    if( !STARTS_WITH(first.c_str(), "_Overview_") ||
                        !STARTS_WITH(second.c_str(), "_Overview_") )
                    {
                        return false;
                    }
                    int nFirst = atoi(first.c_str() + 10);
                    int nSecond = atoi(second.c_str() + 10);
                    return nFirst < nSecond;
                });
    }

    for( const std::string& key: keys)
    {
        if( !STARTS_WITH(key.c_str(), "_Overview_") )
            continue;

        overview_infos.push_back( GetMetadataValue( key ) );
        overview_bands.push_back( nullptr );
        overview_decimations.push_back( atoi(key.c_str()+10) );
    }
}

/************************************************************************/
/*                         UpdateOverviewInfo()                         */
/************************************************************************/
/** Update the in-memory information for an overview.
  * This method will add overview information to the in-memory arrays
  *
  * @param  pszOverviewMDValue  Overview value
  *
  * @param  nFactor             Overview factor i.e. 2, 4, etc
  */
void CPCIDSKChannel::UpdateOverviewInfo(const char *pszOverviewMDValue,
                                        int nFactor)
{
    overview_infos.push_back( pszOverviewMDValue );
    overview_bands.push_back( nullptr );
    overview_decimations.push_back( nFactor );
}

/************************************************************************/
/*                           GetBlockCount()                            */
/************************************************************************/

int CPCIDSKChannel::GetBlockCount() const

{
    // We deliberately call GetBlockWidth() and GetWidth() to trigger
    // computation of the values for tiled layers.  At some point it would
    // be good to cache the block count as this computation is a bit expensive

    int x_block_count = DIV_ROUND_UP(GetWidth(), GetBlockWidth());
    int y_block_count = DIV_ROUND_UP(GetHeight(), GetBlockHeight());

    return x_block_count * y_block_count;
}

/************************************************************************/
/*                          GetOverviewCount()                          */
/************************************************************************/

int CPCIDSKChannel::GetOverviewCount()

{
    EstablishOverviewInfo();

    return static_cast<int>(overview_infos.size());
}

/************************************************************************/
/*                            GetOverview()                             */
/************************************************************************/

PCIDSKChannel *CPCIDSKChannel::GetOverview( int overview_index )

{
    EstablishOverviewInfo();

    if( overview_index < 0 || overview_index >= (int) overview_infos.size() )
        return (PCIDSKChannel*)ThrowPCIDSKExceptionPtr( "Non existent overview (%d) requested.",
                              overview_index );

    if( overview_bands[overview_index] == nullptr )
    {
        PCIDSKBuffer image_header(1024), file_header(1024);
        char  pseudo_filename[65];

        snprintf( pseudo_filename, sizeof(pseudo_filename), "/SIS=%d",
                 atoi(overview_infos[overview_index].c_str()) );

        image_header.Put( pseudo_filename, 64, 64 );

        overview_bands[overview_index] =
            new CTiledChannel( image_header, 0, file_header, -1, file,
                               CHN_UNKNOWN );
    }

    return overview_bands[overview_index];
}

/************************************************************************/
/*                          IsOverviewValid()                           */
/************************************************************************/

bool CPCIDSKChannel::IsOverviewValid( int overview_index )

{
    EstablishOverviewInfo();

    if( overview_index < 0 || overview_index >= (int) overview_infos.size() )
        return ThrowPCIDSKException(0, "Non existent overview (%d) requested.",
                              overview_index ) != 0;

    int sis_id, validity=0;

    sscanf( overview_infos[overview_index].c_str(), "%d %d",
            &sis_id, &validity );

    return validity != 0;
}

/************************************************************************/
/*                       GetOverviewResampling()                        */
/************************************************************************/

std::string CPCIDSKChannel::GetOverviewResampling( int overview_index )

{
    EstablishOverviewInfo();

    if( overview_index < 0 || overview_index >= (int) overview_infos.size() )
    {
        ThrowPCIDSKException( "Non existent overview (%d) requested.",
                              overview_index );
        return "";
    }

    int sis_id, validity=0;
    char resampling[17];

    sscanf( overview_infos[overview_index].c_str(), "%d %d %16s",
            &sis_id, &validity, &(resampling[0]) );

    return resampling;
}

/************************************************************************/
/*                        SetOverviewValidity()                         */
/************************************************************************/

void CPCIDSKChannel::SetOverviewValidity( int overview_index,
                                          bool new_validity )

{
    EstablishOverviewInfo();

    if( overview_index < 0 || overview_index >= (int) overview_infos.size() )
        return ThrowPCIDSKException( "Non existent overview (%d) requested.",
                              overview_index );

    int sis_id, validity=0;
    char resampling[17];

    sscanf( overview_infos[overview_index].c_str(), "%d %d %16s",
            &sis_id, &validity, &(resampling[0]) );

    // are we already set to this value?
    if( new_validity == (validity != 0) )
        return;

    char new_info[48];

    snprintf( new_info, sizeof(new_info), "%d %d %s",
             sis_id, (new_validity ? 1 : 0 ), resampling );

    overview_infos[overview_index] = new_info;

    // write back to metadata.
    char key[20];
    snprintf( key, sizeof(key), "_Overview_%d", overview_decimations[overview_index] );

    SetMetadataValue( key, new_info );
}

/************************************************************************/
/*                        InvalidateOverviews()                         */
/*                                                                      */
/*      Whenever a write is done on this band, we will invalidate       */
/*      any previously valid overviews.                                 */
/************************************************************************/

void CPCIDSKChannel::InvalidateOverviews()

{
    EstablishOverviewInfo();

    for( int i = 0; i < GetOverviewCount(); i++ )
        SetOverviewValidity( i, false );
}

/************************************************************************/
/*                      GetOverviewLevelMapping()                       */
/************************************************************************/

std::vector<int> CPCIDSKChannel::GetOverviewLevelMapping() const
{
    EstablishOverviewInfo();

    return overview_decimations;
}

/************************************************************************/
/*                            GetFilename()                             */
/************************************************************************/
std::string CPCIDSKChannel::GetFilename() const
{
    return file->GetFilename();
}

/************************************************************************/
/*                           GetDescription()                           */
/************************************************************************/

std::string CPCIDSKChannel::GetDescription()

{
    if( ih_offset == 0 )
        return "";

    PCIDSKBuffer ih_1(64);
    std::string ret;

    file->ReadFromFile( ih_1.buffer, ih_offset, 64 );
    ih_1.Get(0,64,ret);

    return ret;
}

/************************************************************************/
/*                           SetDescription()                           */
/************************************************************************/

void CPCIDSKChannel::SetDescription( const std::string &description )

{
    if( ih_offset == 0 )
        return ThrowPCIDSKException( "Description cannot be set on overviews." );

    PCIDSKBuffer ih_1(64);
    ih_1.Put( description.c_str(), 0, 64 );
    file->WriteToFile( ih_1.buffer, ih_offset, 64 );
}

/************************************************************************/
/*                            LoadHistory()                             */
/************************************************************************/

void CPCIDSKChannel::LoadHistory( const PCIDSKBuffer &image_header )

{
    // Read the history from the image header. PCIDSK supports
    // 8 history entries per channel.

    std::string hist_msg;
    history_.clear();
    for (unsigned int i = 0; i < 8; i++)
    {
        image_header.Get(384 + i * 80, 80, hist_msg);

        // Some programs seem to push history records with a trailing '\0'
        // so do some extra processing to cleanup.  FUN records on segment
        // 3 of eltoro.pix are an example of this.
        size_t size = hist_msg.size();
        while( size > 0
               && (hist_msg[size-1] == ' ' || hist_msg[size-1] == '\0') )
            size--;

        hist_msg.resize(size);

        history_.push_back(hist_msg);
    }
}

/************************************************************************/
/*                         GetHistoryEntries()                          */
/************************************************************************/

std::vector<std::string> CPCIDSKChannel::GetHistoryEntries() const
{
    return history_;
}

/************************************************************************/
/*                         SetHistoryEntries()                          */
/************************************************************************/

void CPCIDSKChannel::SetHistoryEntries(const std::vector<std::string> &entries)

{
    if( ih_offset == 0 )
        return ThrowPCIDSKException( "Attempt to update history on a raster that is not\na conventional band with an image header." );

    PCIDSKBuffer image_header(1024);

    file->ReadFromFile( image_header.buffer, ih_offset, 1024 );

    for( unsigned int i = 0; i < 8; i++ )
    {
        const char *msg = "";
        if( entries.size() > i )
            msg = entries[i].c_str();

        image_header.Put( msg, 384 + i * 80, 80 );
    }

    file->WriteToFile( image_header.buffer, ih_offset, 1024 );

    // Force reloading of history_
    LoadHistory( image_header );
}

/************************************************************************/
/*                            PushHistory()                             */
/************************************************************************/

void CPCIDSKChannel::PushHistory( const std::string &app,
                                  const std::string &message )

{
#define MY_MIN(a,b)      ((a<b) ? a : b)

    char current_time[17];
    char history[81];

    GetCurrentDateTime( current_time );

    memset( history, ' ', 80 );
    history[80] = '\0';

    memcpy( history + 0, app.c_str(), MY_MIN(app.size(),7) );
    history[7] = ':';

    memcpy( history + 8, message.c_str(), MY_MIN(message.size(),56) );
    memcpy( history + 64, current_time, 16 );

    std::vector<std::string> history_entries = GetHistoryEntries();

    // coverity[string_null]
    history_entries.insert( history_entries.begin(), history );
    history_entries.resize(8);

    SetHistoryEntries( history_entries );
}

/************************************************************************/
/*                            GetChanInfo()                             */
/************************************************************************/
void CPCIDSKChannel::GetChanInfo( std::string &filename, uint64 &image_offset,
                                  uint64 &pixel_offset, uint64 &line_offset,
                                  bool &little_endian ) const

{
    image_offset = 0;
    pixel_offset = 0;
    line_offset = 0;
    little_endian = true;
    filename = "";
}

/************************************************************************/
/*                            SetChanInfo()                             */
/************************************************************************/

void CPCIDSKChannel::SetChanInfo( CPL_UNUSED std::string filename,
                                  CPL_UNUSED uint64 image_offset,
                                  CPL_UNUSED uint64 pixel_offset,
                                  CPL_UNUSED uint64 line_offset,
                                  CPL_UNUSED bool little_endian )
{
    return ThrowPCIDSKException( "Attempt to SetChanInfo() on a channel that is not FILE interleaved." );
}

/************************************************************************/
/*                            GetEChanInfo()                            */
/************************************************************************/
void CPCIDSKChannel::GetEChanInfo( std::string &filename, int &echannel,
                                   int &exoff, int &eyoff,
                                   int &exsize, int &eysize ) const

{
    echannel = 0;
    exoff = 0;
    eyoff = 0;
    exsize = 0;
    eysize = 0;
    filename = "";
}

/************************************************************************/
/*                            SetEChanInfo()                            */
/************************************************************************/

void CPCIDSKChannel::SetEChanInfo( CPL_UNUSED std::string filename,
                                   CPL_UNUSED int echannel,
                                   CPL_UNUSED int exoff,
                                   CPL_UNUSED int eyoff,
                                   CPL_UNUSED int exsize,
                                   CPL_UNUSED int eysize )
{
    return ThrowPCIDSKException( "Attempt to SetEChanInfo() on a channel that is not FILE interleaved." );
}
