#ifndef PACKJPG_H
#define PACKJPG_H

#define MEM_ERRMSG  "out of memory error"
#define FRD_ERRMSG  "could not read file / file not found: %s"
#define FWR_ERRMSG  "could not write file / file write-protected: %s"
#define MSG_SIZE    128
#define BARLEN      36

// Forward declarations -- real includes are in the source file so we can use 
//                         this header externally
class ArithmeticDecoder;
class ArithmeticEncoder;
class BitReader;
class BitWriter;
class Reader;
class Writer;

/* -----------------------------------------------
    struct declarations
    ----------------------------------------------- */

struct componentInfo
{
    unsigned short* qtable; // quantization table
    int huffdc; // no of huffman table (DC)
    int huffac; // no of huffman table (AC)
    int sfv; // sample factor vertical
    int sfh; // sample factor horizontal
    int mbs; // blocks in mcu
    int bcv; // block count vertical (interleaved)
    int bch; // block count horizontal (interleaved)
    int bc;  // block count (all) (interleaved)
    int ncv; // block count vertical (non interleaved)
    int nch; // block count horizontal (non interleaved)
    int nc;  // block count (all) (non interleaved)
    int sid; // statistical identity
    int jid; // jpeg internal id
};

struct huffCodes
{
    unsigned short cval[256];
    unsigned short clen[256];
    unsigned short max_eobrun;
};

struct huffTree
{
    unsigned short l[256];
    unsigned short r[256];
};

class packJPG
{
public:
    
    packJPG();
    
    ~packJPG();
    
    static const char* pjglib_version_info(void);
    static const char* pjglib_short_name(void);

    /* -----------------------------------------------
    checks if a file exists
    ----------------------------------------------- */
    inline bool file_exists(const char* filename)
    {
        // needed for both, executable and library
        FILE* fp = fopen(filename, "rb");
    
        if (fp == NULL)
        {
            return false;
        }
        else
        {
            fclose(fp);
            return true;
        }
    }
    
    /**************************************************************************
     * Library functions
     *************************************************************************/ 
     
    bool pjglib_convert_stream2stream(char* msg);
    
    bool pjglib_convert_file2file(char* in, char* out, char* msg);
    
    bool pjglib_convert_stream2mem(
        unsigned char** out_file, 
        unsigned int* out_size, 
        char* msg);
        
    void pjglib_init_streams(
        void* in_src, 
        int in_type, 
        int in_size, 
        void* out_dest, 
        int out_type);
        
    /**************************************************************************
     * Main interface functions
     *************************************************************************/
     
    void process_file(void);
    
    void execute(bool (*function)());
    
    /**************************************************************************
     * Main functions
     *************************************************************************/    
     
    bool reset_buffers(void);
    
    bool read_jpeg(void);
    
    bool merge_jpeg(void);
    
    bool decode_jpeg(void);
    
    bool recode_jpeg(void);
    
    bool adapt_icos(void);
    
    bool predict_dc(void);
    
    bool unpredict_dc(void);
    
    bool check_value_range(void);
    
    bool calc_zdst_lists(void);
    
    bool pack_pjg(void);
    
    bool unpack_pjg(void);
    
    /* -----------------------------------------------
    global variables: info about program
    ----------------------------------------------- */
    static const unsigned char appversion;
    static const char*  subversion;
    static const char*  apptitle;
    static const char*  appname;
    static const char*  versiondate;
    static const char*  author;
    static const char   pjg_magic[2];
    
private:

    /**************************************************************************
     * JPEG specific functions
     *************************************************************************/ 
     
    bool jpg_setup_imginfo(void);
    
    bool jpg_parse_jfif(
        unsigned char type, 
        unsigned int len, 
        unsigned char* segment);
        
    bool jpg_rebuild_header(void);
    
    int jpg_decode_block_seq(
        BitReader* huffr, 
        huffTree* dctree, 
        huffTree* actree, 
        short* block);
        
    int jpg_encode_block_seq(
        BitWriter* huffw, 
        huffCodes* dctbl, 
        huffCodes* actbl, 
        short* block);

    int jpg_decode_dc_prg_fs(
        BitReader* huffr, 
        huffTree* dctree, 
        short* block);
        
    int jpg_encode_dc_prg_fs(
        BitWriter* huffw, 
        huffCodes* dctbl, 
        short* block);
        
    int jpg_decode_ac_prg_fs(
        BitReader* huffr, 
        huffTree* actree, 
        short* block, 
        int* eobrun, 
        int from, 
        int to);
        
    int jpg_encode_ac_prg_fs(
        BitWriter* huffw, 
        huffCodes* actbl, 
        short* block, 
        int* eobrun, 
        int from, 
        int to);

    int jpg_decode_dc_prg_sa(BitReader* huffr, short* block);
    
    int jpg_encode_dc_prg_sa(BitWriter* huffw, short* block);
    
    int jpg_decode_ac_prg_sa(
        BitReader* huffr, 
        huffTree* actree, 
        short* block, 
        int* eobrun, 
        int from, 
        int to);
        
    int jpg_encode_ac_prg_sa(
        BitWriter* huffw, 
        std::vector<std::uint8_t>& storw, 
        huffCodes* actbl, 
        short* block, 
        int* eobrun, 
        int from, 
        int to);
        
    int jpg_decode_eobrun_sa(
        BitReader* huffr, 
        short* block, 
        int* eobrun, 
        int from, 
        int to);
        
    int jpg_encode_eobrun(
        BitWriter* huffw, 
        huffCodes* actbl, 
        int* eobrun);
        
    int jpg_encode_crbits(
        BitWriter* huffw, 
        std::vector<std::uint8_t>& storw);
        
    int jpg_next_huffcode(BitReader* huffw, huffTree* ctree);
    
    int jpg_next_mcupos(
        int* mcu, 
        int* cmp, 
        int* csc, 
        int* sub, 
        int* dpos, 
        int* rstw);
        
    int jpg_next_mcuposn(int* cmp, int* dpos, int* rstw);
    
    int jpg_skip_eobrun(int* cmp, int* dpos, int* rstw, int* eobrun);
    
    void jpg_build_huffcodes(
        unsigned char* clen, 
        unsigned char* cval, 
        huffCodes* hc, 
        huffTree* ht);
        
    /**************************************************************************
     * PJG specific functions
     *************************************************************************/ 
    
    bool pjg_encode_zstscan(ArithmeticEncoder* enc, int cmp);
    
    bool pjg_encode_zdst_high(ArithmeticEncoder* enc, int cmp);
    
    bool pjg_encode_zdst_low(ArithmeticEncoder* enc, int cmp);
    
    bool pjg_encode_dc(ArithmeticEncoder* enc, int cmp);
    
    bool pjg_encode_ac_high(ArithmeticEncoder* enc, int cmp);
    
    bool pjg_encode_ac_low(ArithmeticEncoder* enc, int cmp);
    
    bool pjg_encode_generic(
        ArithmeticEncoder* enc, 
        unsigned char* data, 
        int len);
    
    bool pjg_encode_bit(ArithmeticEncoder* enc, unsigned char bit);
    
    bool pjg_decode_zstscan(ArithmeticDecoder* dec, int cmp);
    
    bool pjg_decode_zdst_high(ArithmeticDecoder* dec, int cmp);
    
    bool pjg_decode_zdst_low(ArithmeticDecoder* dec, int cmp);
    
    bool pjg_decode_dc(ArithmeticDecoder* dec, int cmp);
    
    bool pjg_decode_ac_high(ArithmeticDecoder* dec, int cmp);
    
    bool pjg_decode_ac_low(ArithmeticDecoder* dec, int cmp);
    
    bool pjg_decode_generic(
        ArithmeticDecoder* dec, 
        unsigned char** data, 
        int* len);
        
    bool pjg_decode_bit(ArithmeticDecoder* dec, unsigned char* bit);
    
    void pjg_get_zerosort_scan(unsigned char* sv, int cmp);
    
    bool pjg_optimize_header(void);
    
    bool pjg_unoptimize_header(void);
    
    void pjg_aavrg_prepare(
        unsigned short** abs_coeffs, 
        int* weights, 
        unsigned short* abs_store, 
        int cmp);

    int pjg_aavrg_context(
        unsigned short** abs_coeffs, 
        int* weights, 
        int pos, 
        int p_y, 
        int p_x, 
        int r_x);
        
    int pjg_lakh_context(
        signed short** coeffs_x, 
        signed short** coeffs_a, 
        int* pred_cf, 
        int pos);

    void get_context_nnb(int pos, int w, int* a, int* b);
    
    /**************************************************************************
     * DCT functions
     *************************************************************************/ 

    int idct_2d_fst_8x1(int cmp, int dpos, int ix, int iy);
    
    int idct_2d_fst_1x8(int cmp, int dpos, int ix, int iy);

    /**************************************************************************
     * Prediction functions
     *************************************************************************/ 

    int dc_coll_predictor(int cmp, int dpos);
    
    int dc_1ddct_predictor(int cmp, int dpos);
    
    /* -----------------------------------------------
    loco-i predictor
    ----------------------------------------------- */
    inline int plocoi(int a, int b, int c)
    {
        // a -> left; b -> above; c -> above-left

        int min = (a < b) ? a : b;
        int max = (a > b) ? a : b;

        if (c >= max)
        {
            return min;
        }
        if (c <= min)
        {
            return max;
        }

        return a + b - c;
    }
    
    /* -----------------------------------------------
    calculates median out of an integer array
    ----------------------------------------------- */
    inline int median_int(int* values, int size)
    {
        int middle = (size >> 1);
        bool done;
        int swap;
        int i;

        // sort data first
        done = false;
        while (!done)
        {
            done = true;
            for (i = 1; i < size; i++)
            {
                if (values[i] < values[i - 1])
                {
                    swap = values[i];
                    values[i] = values[i - 1];
                    values[i - 1] = swap;
                    done = false;
                }
            }
        }

        // return median
        return ((size % 2) == 0) ?
               (values[middle] + values[middle - 1]) / 2 : values[middle];
    }

    /* -----------------------------------------------
    calculates median out of an float array
    ----------------------------------------------- */
    inline float median_float(float* values, int size)
    {
        int middle = (size >> 1);
        bool done;
        float swap;
        int i;


        // sort data first
        done = false;
        while (!done)
        {
            done = true;
            for (i = 1; i < size; i++)
            {
                if (values[i] < values[i - 1])
                {
                    swap = values[i];
                    values[i] = values[i - 1];
                    values[i - 1] = swap;
                    done = false;
                }
            }
        }

        // return median
        if ((size % 2) == 0)
        {
            return (values[middle] + values[middle - 1]) / 2.0;
        }
        else
        {
            return (values[middle]);
        }
    }

    /**************************************************************************
     * Member variables
     *************************************************************************/ 
    /* -----------------------------------------------
    global variables: library only variables
    ----------------------------------------------- */
    int lib_in_type;
    int lib_out_type;
    
    /* -----------------------------------------------
    global variables: data storage
    ----------------------------------------------- */
    unsigned short qtables[4][64];  // quantization tables
    huffCodes      hcodes[2][4];    // huffman codes
    huffTree       htrees[2][4];    // huffman decoding trees
    unsigned char  htset[2][4];     // 1 if huffman table is set
    
    unsigned char* grbgdata;    // garbage data
    unsigned char* hdrdata ;    // header data
    unsigned char* huffdata;    // huffman coded data
    int            hufs;        // size of huffman data
    int            hdrs;        // size of header
    int            grbs;        // size of garbage
    
    unsigned int*  rstp;        // restart markers positions in huffdata
    unsigned int*  scnp;        // scan start positions in huffdata
    int            rstc;        // count of restart markers
    int            scnc;        // count of scans
    int            rsti;        // restart interval
    char           padbit;      // padbit (for huffman coding)
    unsigned char* rst_err;     // number of wrong-set RST markers per scan
    
    unsigned char* zdstdata[4]; // zero distribution (# of non-zeroes) lists (for higher 7x7 block)
    unsigned char* eobxhigh[4]; // eob in x direction (for higher 7x7 block)
    unsigned char* eobyhigh[4]; // eob in y direction (for higher 7x7 block)
    unsigned char* zdstxlow[4]; // # of non zeroes for first row
    unsigned char* zdstylow[4]; // # of non zeroes for first collumn
    signed short*  colldata[4][64];     // collection sorted DCT coefficients
    
    unsigned char* freqscan[4];         // optimized order for frequency scans (only pointers to scans)
    unsigned char  zsrtscan[4][64];      // zero optimized frequency scan
    
    int adpt_idct_8x8[4][8 * 8 * 8 * 8];    // precalculated/adapted values for idct (8x8)
    int adpt_idct_1x8[4][1 * 1 * 8 * 8];    // precalculated/adapted values for idct (1x8)
    int adpt_idct_8x1[4][8 * 8 * 1 * 1];    // precalculated/adapted values for idct (8x1)
    
    /* -----------------------------------------------
    global variables: info about image
    ----------------------------------------------- */
    componentInfo cmpnfo[4]; // seperate info for each color component
    
    int cmpc;       // component count
    int imgwidth;   // width of image
    int imgheight;  // height of image
    
    int sfhm;       // max horizontal sample factor
    int sfvm;       // max verical sample factor
    int mcuv;       // mcus per line
    int mcuh;       // mcus per collumn
    int mcuc;       // count of mcus
    
    /* -----------------------------------------------
    global variables: info about current scan
    ----------------------------------------------- */
    int cs_cmpc;    // component count in current scan
    int cs_cmp[4];  // component numbers  in current scan
    int cs_from;    // begin - band of current scan ( inclusive )
    int cs_to;      // end - band of current scan ( inclusive )
    int cs_sah;     // successive approximation bit pos high
    int cs_sal;     // successive approximation bit pos low
    
    /* -----------------------------------------------
    global variables: info about files
    ----------------------------------------------- */
    char*  jpgfilename;         // name of JPEG file
    char*  pjgfilename;         // name of PJG file
    int    jpgfilesize;         // size of JPEG file
    int    pjgfilesize;         // size of PJG file
    int    jpegtype;            // type of JPEG coding: 0->unknown, 1->sequential, 2->progressive
    int    filetype;            // type of current file
    std::unique_ptr<Reader> str_in;     // input stream
    std::unique_ptr<Writer> str_out;    // output stream
    
    /* -----------------------------------------------
    global variables: messages
    ----------------------------------------------- */
    char errormessage[MSG_SIZE];
    bool (*errorfunction)();
    
    // meaning of errorlevel:
    //  -1 -> wrong input
    //   0 -> no error
    //   1 -> warning
    //   2 -> fatal error
    int  errorlevel;
    
    /* -----------------------------------------------
    global variables: settings
    ----------------------------------------------- */
    int  err_tol;   // error threshold ( proceed on warnings yes (2) / no (1) )
    bool disc_meta; // discard meta-info yes / no
    bool auto_set;  // automatic find best settings yes/no
    int  action;    // what to do with JPEG/PJG files
    
    unsigned char nois_trs[4];  // bit pattern noise threshold
    unsigned char segm_cnt[4];  // number of segments
};


#endif
