/*
 * The copyright in this software is being made available under the 2-clauses
 * BSD License, included below. This software may be subject to other third
 * party and contributor rights, including patent rights, and no such rights
 * are granted under this license.
 *
 * Copyright (c) 2002-2014, Universite catholique de Louvain (UCL), Belgium
 * Copyright (c) 2002-2014, Professor Benoit Macq
 * Copyright (c) 2003-2007, Francois-Olivier Devaux
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS `AS IS'
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <stdio.h>
#include <math.h>
#include <string.h>
#include "openjpeg.h"
#include "index.h"
#include "opj_inttypes.h"

/* ------------------------------------------------------------------------------------ */

/**
Write a structured index to a file
@param cstr_info Codestream information
@param index Index filename
@return Returns 0 if successful, returns 1 otherwise
*/
int write_index_file(opj_codestream_info_t *cstr_info, char *index)
{
    int tileno, compno, layno, resno, precno, pack_nb, x, y;
    FILE *stream = NULL;
    double total_disto = 0;
    /* UniPG>> */
    int tilepartno;
    char disto_on, numpix_on;

#ifdef USE_JPWL
    if (!strcmp(index, JPWL_PRIVATEINDEX_NAME)) {
        return 0;
    }
#endif /* USE_JPWL */
    /* <<UniPG */

    if (!cstr_info) {
        return 1;
    }

    stream = fopen(index, "w");
    if (!stream) {
        fprintf(stderr, "failed to open index file [%s] for writing\n", index);
        return 1;
    }

    if (cstr_info->tile[0].distotile > 0.0) {
        disto_on = 1;
    } else {
        disto_on = 0;
    }

    if (cstr_info->tile[0].numpix) {
        numpix_on = 1;
    } else {
        numpix_on = 0;
    }

    fprintf(stream, "%d %d\n", cstr_info->image_w, cstr_info->image_h);
    fprintf(stream, "%d\n", cstr_info->prog);
    fprintf(stream, "%d %d\n", cstr_info->tile_x, cstr_info->tile_y);
    fprintf(stream, "%d %d\n", cstr_info->tw, cstr_info->th);
    fprintf(stream, "%d\n", cstr_info->numcomps);
    fprintf(stream, "%d\n", cstr_info->numlayers);
    fprintf(stream, "%d\n", cstr_info->numdecompos[0]); /* based on component 0 */

    for (resno = cstr_info->numdecompos[0]; resno >= 0; resno--) {
        fprintf(stream, "[%d,%d] ",
                (1 << cstr_info->tile[0].pdx[resno]),
                (1 << cstr_info->tile[0].pdx[resno]));    /* based on tile 0 and component 0 */
    }

    fprintf(stream, "\n");
    /* UniPG>> */
    fprintf(stream, "%d\n", cstr_info->main_head_start);
    /* <<UniPG */
    fprintf(stream, "%d\n", cstr_info->main_head_end);
    fprintf(stream, "%d\n", cstr_info->codestream_size);

    fprintf(stream, "\nINFO ON TILES\n");
    fprintf(stream, "tileno start_pos  end_hd  end_tile   nbparts");
    if (disto_on) {
        fprintf(stream, "         disto");
    }
    if (numpix_on) {
        fprintf(stream, "     nbpix");
    }
    if (disto_on && numpix_on) {
        fprintf(stream, "  disto/nbpix");
    }
    fprintf(stream, "\n");

    for (tileno = 0; tileno < cstr_info->tw * cstr_info->th; tileno++) {
        fprintf(stream, "%4d %9d %9d %9d %9d",
                cstr_info->tile[tileno].tileno,
                cstr_info->tile[tileno].start_pos,
                cstr_info->tile[tileno].end_header,
                cstr_info->tile[tileno].end_pos,
                cstr_info->tile[tileno].num_tps);
        if (disto_on) {
            fprintf(stream, " %9e", cstr_info->tile[tileno].distotile);
        }
        if (numpix_on) {
            fprintf(stream, " %9d", cstr_info->tile[tileno].numpix);
        }
        if (disto_on && numpix_on) {
            fprintf(stream, " %9e", cstr_info->tile[tileno].distotile /
                    cstr_info->tile[tileno].numpix);
        }
        fprintf(stream, "\n");
    }

    for (tileno = 0; tileno < cstr_info->tw * cstr_info->th; tileno++) {
        OPJ_OFF_T start_pos, end_ph_pos, end_pos;
        double disto = 0;
        int max_numdecompos = 0;
        pack_nb = 0;

        for (compno = 0; compno < cstr_info->numcomps; compno++) {
            if (max_numdecompos < cstr_info->numdecompos[compno]) {
                max_numdecompos = cstr_info->numdecompos[compno];
            }
        }

        fprintf(stream, "\nTILE %d DETAILS\n", tileno);
        fprintf(stream,
                "part_nb tileno  start_pack num_packs  start_pos end_tph_pos   end_pos\n");
        for (tilepartno = 0; tilepartno < cstr_info->tile[tileno].num_tps; tilepartno++)
            fprintf(stream, "%4d %9d   %9d %9d  %9d %11d %9d\n",
                    tilepartno, tileno,
                    cstr_info->tile[tileno].tp[tilepartno].tp_start_pack,
                    cstr_info->tile[tileno].tp[tilepartno].tp_numpacks,
                    cstr_info->tile[tileno].tp[tilepartno].tp_start_pos,
                    cstr_info->tile[tileno].tp[tilepartno].tp_end_header,
                    cstr_info->tile[tileno].tp[tilepartno].tp_end_pos
                   );

        if (cstr_info->prog == OPJ_LRCP) {  /* LRCP */
            fprintf(stream,
                    "LRCP\npack_nb tileno layno resno compno precno start_pos end_ph_pos end_pos");
            if (disto_on) {
                fprintf(stream, " disto");
            }
            fprintf(stream, "\n");

            for (layno = 0; layno < cstr_info->numlayers; layno++) {
                for (resno = 0; resno < max_numdecompos + 1; resno++) {
                    for (compno = 0; compno < cstr_info->numcomps; compno++) {
                        int prec_max;
                        if (resno > cstr_info->numdecompos[compno]) {
                            break;
                        }
                        prec_max = cstr_info->tile[tileno].pw[resno] *
                                   cstr_info->tile[tileno].ph[resno];
                        for (precno = 0; precno < prec_max; precno++) {
                            start_pos = cstr_info->tile[tileno].packet[pack_nb].start_pos;
                            end_ph_pos = cstr_info->tile[tileno].packet[pack_nb].end_ph_pos;
                            end_pos = cstr_info->tile[tileno].packet[pack_nb].end_pos;
                            disto = cstr_info->tile[tileno].packet[pack_nb].disto;
                            fprintf(stream, "%4d %6d %7d %5d %6d  %6d    %6" PRId64 "     %6" PRId64 " %7"
                                    PRId64,
                                    pack_nb, tileno, layno, resno, compno, precno, start_pos, end_ph_pos, end_pos);
                            if (disto_on) {
                                fprintf(stream, " %8e", disto);
                            }
                            fprintf(stream, "\n");
                            total_disto += disto;
                            pack_nb++;
                        }
                    }
                }
            }
        } /* LRCP */

        else if (cstr_info->prog == OPJ_RLCP) { /* RLCP */
            fprintf(stream,
                    "RLCP\npack_nb tileno resno layno compno precno start_pos end_ph_pos end_pos\n");
            if (disto_on) {
                fprintf(stream, " disto");
            }
            fprintf(stream, "\n");

            for (resno = 0; resno < max_numdecompos + 1; resno++) {
                for (layno = 0; layno < cstr_info->numlayers; layno++) {
                    for (compno = 0; compno < cstr_info->numcomps; compno++) {
                        int prec_max;
                        if (resno > cstr_info->numdecompos[compno]) {
                            break;
                        }
                        prec_max = cstr_info->tile[tileno].pw[resno] *
                                   cstr_info->tile[tileno].ph[resno];
                        for (precno = 0; precno < prec_max; precno++) {
                            start_pos = cstr_info->tile[tileno].packet[pack_nb].start_pos;
                            end_ph_pos = cstr_info->tile[tileno].packet[pack_nb].end_ph_pos;
                            end_pos = cstr_info->tile[tileno].packet[pack_nb].end_pos;
                            disto = cstr_info->tile[tileno].packet[pack_nb].disto;
                            fprintf(stream, "%4d %6d %5d %7d %6d %6d %9" PRId64 "   %9" PRId64 " %7" PRId64,
                                    pack_nb, tileno, resno, layno, compno, precno, start_pos, end_ph_pos, end_pos);
                            if (disto_on) {
                                fprintf(stream, " %8e", disto);
                            }
                            fprintf(stream, "\n");
                            total_disto += disto;
                            pack_nb++;
                        }
                    }
                }
            }
        } /* RLCP */

        else if (cstr_info->prog == OPJ_RPCL) { /* RPCL */

            fprintf(stream,
                    "RPCL\npack_nb tileno resno precno compno layno start_pos end_ph_pos end_pos");
            if (disto_on) {
                fprintf(stream, " disto");
            }
            fprintf(stream, "\n");

            for (resno = 0; resno < max_numdecompos + 1; resno++) {
                int numprec = cstr_info->tile[tileno].pw[resno] *
                              cstr_info->tile[tileno].ph[resno];
                for (precno = 0; precno < numprec; precno++) {
                    /* I suppose components have same XRsiz, YRsiz */
                    int x0 = cstr_info->tile_Ox + tileno - (int)floor((float)tileno /
                             (float)cstr_info->tw) * cstr_info->tw * cstr_info->tile_x;
                    int y0 = cstr_info->tile_Ox + (int)floor((float)tileno /
                             (float)cstr_info->tw) * cstr_info->tile_y;
                    int x1 = x0 + cstr_info->tile_x;
                    int y1 = y0 + cstr_info->tile_y;
                    for (compno = 0; compno < cstr_info->numcomps; compno++) {
                        int pcnx = cstr_info->tile[tileno].pw[resno];
                        int pcx = (int) pow(2, cstr_info->tile[tileno].pdx[resno] +
                                            cstr_info->numdecompos[compno] - resno);
                        int pcy = (int) pow(2, cstr_info->tile[tileno].pdy[resno] +
                                            cstr_info->numdecompos[compno] - resno);
                        int precno_x = precno - (int) floor((float)precno / (float)pcnx) * pcnx;
                        int precno_y = (int) floor((float)precno / (float)pcnx);
                        if (resno > cstr_info->numdecompos[compno]) {
                            break;
                        }
                        for (y = y0; y < y1; y++) {
                            if (precno_y * pcy == y) {
                                for (x = x0; x < x1; x++) {
                                    if (precno_x * pcx == x) {
                                        for (layno = 0; layno < cstr_info->numlayers; layno++) {
                                            start_pos = cstr_info->tile[tileno].packet[pack_nb].start_pos;
                                            end_ph_pos = cstr_info->tile[tileno].packet[pack_nb].end_ph_pos;
                                            end_pos = cstr_info->tile[tileno].packet[pack_nb].end_pos;
                                            disto = cstr_info->tile[tileno].packet[pack_nb].disto;
                                            fprintf(stream, "%4d %6d %5d %6d %6d %7d %9" PRId64 "   %9" PRId64 " %7" PRId64,
                                                    pack_nb, tileno, resno, precno, compno, layno, start_pos, end_ph_pos, end_pos);
                                            if (disto_on) {
                                                fprintf(stream, " %8e", disto);
                                            }
                                            fprintf(stream, "\n");
                                            total_disto += disto;
                                            pack_nb++;
                                        }
                                    }
                                }/* x = x0..x1 */
                            }
                        }  /* y = y0..y1 */
                    } /* precno */
                } /* compno */
            } /* resno */
        } /* RPCL */

        else if (cstr_info->prog == OPJ_PCRL) { /* PCRL */
            /* I suppose components have same XRsiz, YRsiz */
            int x0 = cstr_info->tile_Ox + tileno - (int)floor((float)tileno /
                     (float)cstr_info->tw) * cstr_info->tw * cstr_info->tile_x;
            int y0 = cstr_info->tile_Ox + (int)floor((float)tileno /
                     (float)cstr_info->tw) * cstr_info->tile_y;
            int x1 = x0 + cstr_info->tile_x;
            int y1 = y0 + cstr_info->tile_y;

            /* Count the maximum number of precincts */
            int max_numprec = 0;
            for (resno = 0; resno < max_numdecompos + 1; resno++) {
                int numprec = cstr_info->tile[tileno].pw[resno] *
                              cstr_info->tile[tileno].ph[resno];
                if (numprec > max_numprec) {
                    max_numprec = numprec;
                }
            }

            fprintf(stream,
                    "PCRL\npack_nb tileno precno compno resno layno start_pos end_ph_pos end_pos");
            if (disto_on) {
                fprintf(stream, " disto");
            }
            fprintf(stream, "\n");

            for (precno = 0; precno < max_numprec; precno++) {
                for (compno = 0; compno < cstr_info->numcomps; compno++) {
                    for (resno = 0; resno < cstr_info->numdecompos[compno] + 1; resno++) {
                        int numprec = cstr_info->tile[tileno].pw[resno] *
                                      cstr_info->tile[tileno].ph[resno];
                        int pcnx = cstr_info->tile[tileno].pw[resno];
                        int pcx = (int) pow(2, cstr_info->tile[tileno].pdx[resno] +
                                            cstr_info->numdecompos[compno] - resno);
                        int pcy = (int) pow(2, cstr_info->tile[tileno].pdy[resno] +
                                            cstr_info->numdecompos[compno] - resno);
                        int precno_x = precno - (int) floor((float)precno / (float)pcnx) * pcnx;
                        int precno_y = (int) floor((float)precno / (float)pcnx);
                        if (precno >= numprec) {
                            continue;
                        }
                        for (y = y0; y < y1; y++) {
                            if (precno_y * pcy == y) {
                                for (x = x0; x < x1; x++) {
                                    if (precno_x * pcx == x) {
                                        for (layno = 0; layno < cstr_info->numlayers; layno++) {
                                            start_pos = cstr_info->tile[tileno].packet[pack_nb].start_pos;
                                            end_ph_pos = cstr_info->tile[tileno].packet[pack_nb].end_ph_pos;
                                            end_pos = cstr_info->tile[tileno].packet[pack_nb].end_pos;
                                            disto = cstr_info->tile[tileno].packet[pack_nb].disto;
                                            fprintf(stream, "%4d %6d %6d %6d %5d %7d %9" PRId64 "   %9" PRId64 " %7" PRId64,
                                                    pack_nb, tileno, precno, compno, resno, layno, start_pos, end_ph_pos, end_pos);
                                            if (disto_on) {
                                                fprintf(stream, " %8e", disto);
                                            }
                                            fprintf(stream, "\n");
                                            total_disto += disto;
                                            pack_nb++;
                                        }
                                    }
                                }/* x = x0..x1 */
                            }
                        }  /* y = y0..y1 */
                    } /* resno */
                } /* compno */
            } /* precno */
        } /* PCRL */

        else {  /* CPRL */
            /* Count the maximum number of precincts */
            int max_numprec = 0;
            for (resno = 0; resno < max_numdecompos + 1; resno++) {
                int numprec = cstr_info->tile[tileno].pw[resno] *
                              cstr_info->tile[tileno].ph[resno];
                if (numprec > max_numprec) {
                    max_numprec = numprec;
                }
            }

            fprintf(stream,
                    "CPRL\npack_nb tileno compno precno resno layno start_pos end_ph_pos end_pos");
            if (disto_on) {
                fprintf(stream, " disto");
            }
            fprintf(stream, "\n");

            for (compno = 0; compno < cstr_info->numcomps; compno++) {
                /* I suppose components have same XRsiz, YRsiz */
                int x0 = cstr_info->tile_Ox + tileno - (int)floor((float)tileno /
                         (float)cstr_info->tw) * cstr_info->tw * cstr_info->tile_x;
                int y0 = cstr_info->tile_Ox + (int)floor((float)tileno /
                         (float)cstr_info->tw) * cstr_info->tile_y;
                int x1 = x0 + cstr_info->tile_x;
                int y1 = y0 + cstr_info->tile_y;

                for (precno = 0; precno < max_numprec; precno++) {
                    for (resno = 0; resno < cstr_info->numdecompos[compno] + 1; resno++) {
                        int numprec = cstr_info->tile[tileno].pw[resno] *
                                      cstr_info->tile[tileno].ph[resno];
                        int pcnx = cstr_info->tile[tileno].pw[resno];
                        int pcx = (int) pow(2, cstr_info->tile[tileno].pdx[resno] +
                                            cstr_info->numdecompos[compno] - resno);
                        int pcy = (int) pow(2, cstr_info->tile[tileno].pdy[resno] +
                                            cstr_info->numdecompos[compno] - resno);
                        int precno_x = precno - (int) floor((float)precno / (float)pcnx) * pcnx;
                        int precno_y = (int) floor((float)precno / (float)pcnx);
                        if (precno >= numprec) {
                            continue;
                        }

                        for (y = y0; y < y1; y++) {
                            if (precno_y * pcy == y) {
                                for (x = x0; x < x1; x++) {
                                    if (precno_x * pcx == x) {
                                        for (layno = 0; layno < cstr_info->numlayers; layno++) {
                                            start_pos = cstr_info->tile[tileno].packet[pack_nb].start_pos;
                                            end_ph_pos = cstr_info->tile[tileno].packet[pack_nb].end_ph_pos;
                                            end_pos = cstr_info->tile[tileno].packet[pack_nb].end_pos;
                                            disto = cstr_info->tile[tileno].packet[pack_nb].disto;
                                            fprintf(stream, "%4d %6d %6d %6d %5d %7d %9" PRId64 "   %9" PRId64 " %7" PRId64,
                                                    pack_nb, tileno, compno, precno, resno, layno, start_pos, end_ph_pos, end_pos);
                                            if (disto_on) {
                                                fprintf(stream, " %8e", disto);
                                            }
                                            fprintf(stream, "\n");
                                            total_disto += disto;
                                            pack_nb++;
                                        }
                                    }
                                }/* x = x0..x1 */
                            }
                        } /* y = y0..y1 */
                    } /* resno */
                } /* precno */
            } /* compno */
        } /* CPRL */
    } /* tileno */

    if (disto_on) {
        fprintf(stream, "%8e\n", cstr_info->D_max); /* SE max */
        fprintf(stream, "%.8e\n", total_disto); /* SE totale */
    }
    /* UniPG>> */
    /* print the markers' list */
    if (cstr_info->marknum) {
        fprintf(stream, "\nMARKER LIST\n");
        fprintf(stream, "%d\n", cstr_info->marknum);
        fprintf(stream, "type\tstart_pos    length\n");
        for (x = 0; x < cstr_info->marknum; x++) {
            fprintf(stream, "%X\t%9" PRId64 " %9d\n", cstr_info->marker[x].type,
                    cstr_info->marker[x].pos, cstr_info->marker[x].len);
        }
    }
    /* <<UniPG */
    fclose(stream);

    fprintf(stderr, "Generated index file %s\n", index);

    return 0;
}
