/*  Copyright (c) MediaArea.net SARL. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license that can
 *  be found in the License.html file in the root of the source tree.
 */

//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//
// Information about FFV1 files
//
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

//---------------------------------------------------------------------------
#ifndef MediaInfo_Ffv1H
#define MediaInfo_Ffv1H
//---------------------------------------------------------------------------

//---------------------------------------------------------------------------
#include "MediaInfo/File__Analyze.h"
//---------------------------------------------------------------------------

namespace MediaInfoLib
{

//***************************************************************************
// Class RangeCoder
//***************************************************************************

const size_t states_size=32;
const size_t state_transitions_size=256;
typedef int8u states[states_size];
typedef int8u state_transitions[state_transitions_size];
typedef int8u **states_context_plane;

const size_t MAX_PLANES=4;
const size_t MAX_QUANT_TABLES=8;
const size_t MAX_CONTEXT_INPUTS=5;

class RangeCoder;

//***************************************************************************
// Class Slice
//***************************************************************************

const int32u RUN_MODE_STOP = 0;
const int32u RUN_MODE_PROCESSING = 1;
const int32u RUN_MODE_INTERRUPTED = 2;

namespace FFV1
{
    typedef int32s pixel_t;
}

class Slice
{
public:

    Slice() : run_index(0), run_mode(RUN_MODE_STOP),
        sample_buffer(NULL)
    {
        for (size_t i = 0; i < MAX_QUANT_TABLES; ++i)
            plane_states[i] = NULL;

        for (size_t i = 0; i < MAX_PLANES; ++i)
            contexts[i] = NULL;
    }

    ~Slice()
    {
        if (sample_buffer)
        {
            delete [] sample_buffer;
            sample_buffer = NULL;
        }
        contexts_clean();
    }

    void    sample_buffer_new(size_t size)
    {
        if (sample_buffer)
        {
            delete [] sample_buffer;
            sample_buffer = NULL;
        }
        sample_buffer = new FFV1::pixel_t[size];
    }

    void    run_index_init() { run_index=0; }
    void    run_mode_init() { run_segment_length = 0; run_mode=RUN_MODE_STOP; }

    //TEMP
    int32u  slice_x;
    int32u  slice_y;
    int32u  slice_w;
    int32u  slice_h;
    int32u  x;
    int32u  y;
    int32u  w;
    int32u  h;
    int32u  run_index;
    int32u  run_mode;
    int32s  run_segment_length;
    FFV1::pixel_t* sample_buffer;

    class Context
    {
    public:
        static const int32u N0; // Determined threshold to divide N and B
        static const int32s Cmax; // Storage limitation, C never upper than 127
        static const int32s Cmin; // Storage limitation, C never below than -128
        int32s N; // Count where the context was encountred
        int32s B; // Accumulated sum of corrected prediction residuals
        int32s A; // Accumulated sum of the absolute corrected prediction residuals (St + Nt)
        int32s C; // Correction value

        Context() :
            N(1),
            B(0),
            A(4),
            C(0)
        {}

        inline void update_correlation_value_and_shift()
        {
            // Step 11: Resets
            if ((int64u)N == N0)
            {
                // divide by 2, if >= 0 : ROUND, otherwise, CEIL
                N >>= 1;
                A >>= 1;
                B >>= 1;
            }
            ++N; // context meets

                 // Step 12: Bias computation procedure
                 // Keep B in (-N;0]
            if (B <= -N)
            {
                if (C > Cmin)
                    --C;

                B += N;
                if (B <= -N)
                    B = -N + 1;
            }
            else if (B > 0)
            {
                if (C < Cmax)
                    ++C;

                if (B > N)
                    B = 0;
                else
                    B -= N;
            }
        }
        inline int compute_k()
        {
            int k = 0;
            while ((N << k) < A)
                ++k;
            return k;
        }
    };

    typedef Context* ContextPtr;

    ContextPtr contexts[MAX_PLANES];
    states_context_plane plane_states[MAX_QUANT_TABLES];
    size_t               plane_states_maxsizes[MAX_QUANT_TABLES];

    // HELPER
    void contexts_init(int32u plane_count, int32u quant_table_index[MAX_PLANES], int32u context_count[MAX_QUANT_TABLES]);
    void contexts_clean();
};

//***************************************************************************
// Class File_Ffv1
//***************************************************************************

typedef FFV1::pixel_t quant_table_struct2[256];
typedef quant_table_struct2 quant_table_struct[MAX_CONTEXT_INPUTS];

class File_Ffv1 : public File__Analyze
{
public :
    //Constructor/Destructor
    File_Ffv1();
    ~File_Ffv1();

    //Input
    int32u Width;
    int32u Height;

private :
    //Streams management
    void Streams_Accept();

    //Buffer - Global
    void Read_Buffer_OutOfBand();
    void Read_Buffer_Continue();
    void Skip_Frame();

    //Elements
    void   Parameters();
    bool   SliceHeader(states &States);
    void   SliceContent(states &States);
    int32s get_symbol_with_bias_correlation(Slice::ContextPtr context);
    void rgb();
    void plane(int32u pos);
    void line(int pos, FFV1::pixel_t *sample[2]);
    int32s pixel_GR(int32s context);
    int32s pixel_RC(int32s context);
    bool QuantizationTable(size_t i);
    bool QuantizationTablePerContext(size_t i, size_t j, FFV1::pixel_t &scale);
    void copy_plane_states_to_slice(int8u plane_count);

    //Range coder
    #if MEDIAINFO_TRACE
        void Get_RB (states &States, bool  &Info, const char* Name);
        void Get_RU (states &States, int32u &Info, const char* Name);
        void Get_RS (states &States, int32s &Info, const char* Name);
        void Get_RS (int8u* &States, int32s &Info, const char* Name);
        void Skip_RC(states &States,              const char* Name);
        void Skip_RU(states &States,              const char* Name);
        void Skip_RS(states &States,              const char* Name);
        #define Info_RC(_STATE, _INFO, _NAME) bool  _INFO; Get_RB (_STATE, _INFO, _NAME)
        #define Info_RU(_STATE, _INFO, _NAME) int32u _INFO; Get_RU (_STATE, _INFO, _NAME)
        #define Info_RS(_STATE, _INFO, _NAME) int32s _INFO; Get_RS (_STATE, _INFO, _NAME)
    #else //MEDIAINFO_TRACE
        void Get_RB_ (states &States, bool  &Info);
        void Get_RU_ (states &States, int32u &Info);
        void Get_RS_ (states &States, int32s &Info);
        void Get_RS_ (int8u* &States, int32s &Info);
        #define Get_RB(Bits, Info, Name) Get_RB_(Bits, Info)
        #define Get_RU(Bits, Info, Name) Get_RU_(Bits, Info)
        #define Get_RS(Bits, Info, Name) Get_RS_(Bits, Info)
        void Skip_RC_(states &States);
        void Skip_RU_(states &States);
        void Skip_RS_(states &States);
        #define Skip_RC(Bits, Name) Skip_RC_(Bits)
        #define Skip_RU(Bits, Name) Skip_RU_(Bits)
        #define Skip_RS(Bits, Name) Skip_RS_(Bits)
        #define Info_RC(_STATE, _INFO, _NAME) Skip_RC_(_STATE)
        #define Info_RU(_STATE, _INFO, _NAME) Skip_RU_(_STATE)
        #define Info_RS(_STATE, _INFO, _NAME) Skip_RS_(_STATE)
        static constexpr auto Trace_Activated=false;
    #endif //MEDIAINFO_TRACE
    #if MEDIAINFO_MACROBLOCKS
        int     ParseCompletely;
    #else //MEDIAINFO_MACROBLOCKS
        static constexpr int ParseCompletely=0;
    #endif //MEDIAINFO_MACROBLOCKS
    RangeCoder* RC;
    Slice *slices;
    Slice *current_slice;

    //From Parameters
    int32u  version;
    int32u  micro_version;
    int32u  coder_type;
    int32u  colorspace_type;
    int32u  bits_per_raw_sample;
    int32u  h_chroma_subsample_log2;
    int32u  v_chroma_subsample_log2;
    int32u  num_h_slices;
    int32u  num_v_slices;
    int32u  ec;
    int32u  intra;
    bool    alpha_plane;

    //Temp
    bool    Parameters_IsValid;
    bool    ConfigurationRecord_IsPresent;
    bool    KeyFramePassed;
    bool    is_overflow_16bit;
    int32u  context_count[MAX_QUANT_TABLES];
    quant_table_struct quant_tables[MAX_QUANT_TABLES];
    int32u  quant_table_index[MAX_PLANES];
    int32u  quant_table_count;
    int32u  picture_structure;
    int32u  sample_aspect_ratio_num;
    int32u  sample_aspect_ratio_den;
    bool    keyframe;
    bool    chroma_planes;
    bool    BuggySlices;
    state_transitions state_transitions_table;
    size_t  quant_table_index_count;

    //TEMP
    static const int32u PREFIX_MAX = 12; //limit
    int8u bits_max;
    int32s bits_mask1;
    int32s bits_mask2;
    int32s bits_mask3;
    states_context_plane Context_RC; // Range Coder context
    Slice::ContextPtr    Context_GR; // Rice Golomb context
    size_t               Context_GR_Size;
    size_t x;

    states_context_plane plane_states[MAX_QUANT_TABLES];
    size_t               plane_states_maxsizes[MAX_QUANT_TABLES];

    int32s golomb_rice_decode(int k);
    void plane_states_clean(states_context_plane states[MAX_QUANT_TABLES]);
};

} //NameSpace

#endif
