#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#ifdef WIN32
#define M_PI 3.1415926
#endif

// ANL : for bits which photon used according to normal
#define MAXGATHERED 1000010

#include "photonmap.h"

#define VARIANCE 0.05           //percent: orginal 0.05, 2.5
#define INCLUDE_DIRECT_ILLUM false

/* This is the constructor for the photon map.
 * To create the photon map it is necessary to specify the
 * maximum number of photons that will be stored
*/
//************************************************
PhotonMap::PhotonMap (const int max_phot, const int type)
//************************************************
{
    stored_photons = 0;
    prev_scale = 1;
    max_photons = max_phot;
    map_type = type;

    photons = (Photon *) malloc (sizeof (Photon) * (max_photons + 1));

    if (photons == NULL) {
        fprintf (stderr, "Out of memory initializing photon map\n");
        exit (-1);
    }

    bbox_min.x = bbox_min.y = bbox_min.z = 1e8f;
    bbox_max.x = bbox_max.y = bbox_max.z = -1e8f;

    //----------------------------------------
    // initialize direction conversion tables
    //----------------------------------------

    for (int i = 0; i < 256; i++) {
        double angle = double (i) * (1.0 / 256.0) * M_PI;
        costheta[i] = cos (angle);
        sintheta[i] = sin (angle);
        cosphi[i] = cos (2.0 * angle);
        sinphi[i] = sin (2.0 * angle);
    }
}


//*************************
PhotonMap::~PhotonMap ()
//*************************
{
    free (photons);
}


//************************************************
void
PhotonMap::reInit (const int max_phot)
//************************************************
{
    free(photons);

    max_photons = max_phot;
    photons = (Photon *) malloc (sizeof (Photon) * (max_photons + 1));

    if (photons == NULL) {
        fprintf (stderr, "Out of memory initializing photon map\n");
        exit (-1);
    }
}


//*************************
void
PhotonMap::Clear ()
//*************************
{
    stored_photons = 0;
    prev_scale = 1;
    bbox_min.x = bbox_min.y = bbox_min.z = 1e8f;
    bbox_max.x = bbox_max.y = bbox_max.z = -1e8f;
}


/* photon_dir returns the direction of a photon
 */
//*****************************************************************
void
PhotonMap::photon_dir (R3 & dir, const Photon * p) const
//*****************************************************************
{
    dir.x = sintheta[p->theta] * cosphi[p->phi];
    dir.y = sintheta[p->theta] * sinphi[p->phi];
    dir.z = costheta[p->theta];
}


/* irradiance_estimate computes an irradiance estimate
 * at a given surface position
*/
//**********************************************
void
PhotonMap::irradiance_estimate_all (R3 & irrad,     // returned irradiance
                                    const R3 pos,   // surface position
                                    const R3 normal,        // surface normal at pos
                                    const real max_dist,    // max distance to look for photons
                                    const int nphotons) const       // number of photons to use
//**********************************************
{
    int number_read = 0;
    irrad.x = irrad.y = irrad.z = 0.0;

    NearestPhotons np;
#ifdef WIN32
    np.dist2 = (real *) malloc (sizeof (real) * (nphotons + 1));
    np.index = (const Photon **) malloc (sizeof (Photon *) * (nphotons + 1));
#else
    np.dist2 = (real *) alloca (sizeof (real) * (nphotons + 1));
    np.index = (const Photon **) alloca (sizeof (Photon *) * (nphotons + 1));
#endif

    np.pos.x = pos.x;
    np.pos.y = pos.y;
    np.pos.z = pos.z;
    np.max = nphotons;
    np.found = 0;
    np.got_heap = 0;
    np.dist2[0] = max_dist * max_dist;
#ifdef PACKARDMODE
    //np.normal.x = normal.x; np.normal.y = normal.y; np.normal.y = normal.y;
#endif

    // locate the nearest photons
    locate_photons (&np, 1);

    // if less than 8 photons return
    if (np.found < 8)
        return;

    //R3 pdir;

// sum irradiance from all photons
    for (int i = 1; i <= np.found; i++) {
        const Photon *p = np.index[i];
#ifdef PACKARDMODE
        // try not to include drastically varying normals

#ifdef PHOTONDIRCHECK
        R3 pdir;                         // Experimental requires scaling !!!!!
        photon_dir(pdir, p);
        if ( Dot(normal, pdir) > 0 )  {  // Sum all photons with good direct.
#else
        if ( Dot(normal, p->normal) > (1.0-VARIANCE)*(1.0-VARIANCE) ) {
#endif

            /*       if ((fabs (normal.x - p->normal.x) <= fabs (normal.x * VARIANCE) &&
             *            fabs (normal.y - p->normal.y) <= fabs (normal.y * VARIANCE) &&
             *            fabs (normal.z - p->normal.z) <= fabs (normal.z * VARIANCE)) ||
             *           // normal is reversed ;) ANL!!!
             *           (fabs (normal.x + p->normal.x) <= fabs (normal.x * VARIANCE) &&
             *            fabs (normal.y + p->normal.y) <= fabs (normal.y * VARIANCE) &&
             *            fabs (normal.z + p->normal.z) <= fabs (normal.z * VARIANCE))
             *          ) 
             *       {
             */
#else
        // the photon_dir call and following if can be omitted (for speed)
        // if the scene does not have any thin surfaces
        //!!!    photon_dir( pdir, p );
        //!!!    if ( (pdir.x*normal.x+pdir.y*normal.y+pdir.z*normal.z) < 0.0f )
        {
            number_read++;
#endif

#ifdef NO_FILTER
            Add (irrad, irrad, p->power);
#else
#define CONE_FILTER yes
#ifdef CONE_FILTER
            const real filter_k = 1.0;
            const real scale_by =
                1.0 - sqrt (np.dist2[i]) / (filter_k * sqrt (np.dist2[0]));
            AddMult (irrad, irrad, p->power, scale_by);
#endif
#endif
//         irrad.x += p->power.x;
//         irrad.y += p->power.y;
//         irrad.z += p->power.z;
        }
    }
    // estimate of density
#ifdef NO_FILTER
    const real tmp = (1.0f / M_PI) / (np.dist2[0]);
#else
#ifdef CONE_FILTER
    const real filter_k = 1.0;   // Twice defined !!!
    const real tmp =
        (1.0f / M_PI) / (np.dist2[0] * (1.0 - 0.66666666666 / filter_k));
#endif
#endif
    irrad.x *= tmp;
    irrad.y *= tmp;
    irrad.z *= tmp;
}


//**********************************************----------------------------
void
PhotonMap::irradiance_estimate (R3 & irrad,     // returned irradiance
                                const R3 pos,   // surface position
                                const R3 normal,        // surface normal at pos
                                const real max_dist,    // max distance to look for photons
                                const int nphotons) const       // number of photons to use
//**********************************************
{
    int number_read = 0;
    irrad.x = irrad.y = irrad.z = 0.0;

    NearestPhotons np;
#ifdef WIN32
    np.dist2 = (real *) malloc (sizeof (real) * (nphotons + 1));
    np.index = (const Photon **) malloc (sizeof (Photon *) * (nphotons + 1));
#else
    np.dist2 = (real *) alloca (sizeof (real) * (nphotons + 1));
    np.index = (const Photon **) alloca (sizeof (Photon *) * (nphotons + 1));
#endif

    np.pos.x = pos.x;
    np.pos.y = pos.y;
    np.pos.z = pos.z;
    np.max = nphotons;
    np.found = 0;
    np.got_heap = 0;
    np.dist2[0] = max_dist * max_dist;

    // locate the nearest photons
    locate_photons (&np, 1);

    // if less than 8 photons return
    if (np.found < 8)
        return;

    //R3 pdir;

// sum irradiance from all photons
#ifdef GRADIENT
    R3 photon_sum(0,0,0);
    int used_photons = 0;
// Gradient --------------------------------------------
// only no-filterversion now in work
#ifdef NO_FILTER
    for (int i = 1; i <= np.found; i++) {
        const Photon *p = np.index[i];
        if ( Dot(normal, p->normal) > (1.0-VARIANCE)*(1.0-VARIANCE) ) {
            Add (photon_sum, photon_sum, p->pos);
            used_photons++;
        }
    }
    SubMult(photon_sum, pos, used_photons);
    Mult(irrad, photon_sum, (1.0/used_photons)/max_dist); ///sqrt(np.dist2[0]));
    return;

    for (int i = 1; i <= np.found; i++) {
        const Photon *p = np.index[i];

        if ( Dot(normal, p->normal) > (1.0-VARIANCE)*(1.0-VARIANCE) ) {
            Add (irrad, irrad, p->power);
        }
    }
    // estimate of density
    Mult(irrad, (1.0f / M_PI) / (np.dist2[0]) );
#else
    // Cone filter
    const real filter_k = 1.0;
    for (int i = 1; i <= np.found; i++) {
        const Photon *p = np.index[i];

        if ( Dot(normal, p->normal) > (1.0-VARIANCE)*(1.0-VARIANCE) ) {
            const real scale_by =
                1.0 - sqrt (np.dist2[i]) / (filter_k * sqrt (np.dist2[0]));
            AddMult (irrad, irrad, p->power, scale_by);
        }
    }
    // estimate of density
    Mult( irrad,
          (1.0f / M_PI) / (np.dist2[0] * (1.0 - 0.66666666666 / filter_k))
        );
#endif
#else
    // Nogradient ----------------------------------------
#ifdef NO_FILTER
    for (int i = 1; i <= np.found; i++) {
        const Photon *p = np.index[i];

        if ( Dot(normal, p->normal) > (1.0-VARIANCE)*(1.0-VARIANCE) ) {
            Add (irrad, irrad, p->power);
        }
    }
    // estimate of density
    Mult(irrad, (1.0f / M_PI) / (np.dist2[0]) );
#else
    // Cone filter
    const real filter_k = 1.0;
    for (int i = 1; i <= np.found; i++) {
        const Photon *p = np.index[i];

        if ( Dot(normal, p->normal) > (1.0-VARIANCE)*(1.0-VARIANCE) ) {
            const real scale_by =
                1.0 - sqrt (np.dist2[i]) / (filter_k * sqrt (np.dist2[0]));
            AddMult (irrad, irrad, p->power, scale_by);
        }
    }
    // estimate of density
    Mult( irrad,
          (1.0f / M_PI) / (np.dist2[0] * (1.0 - 0.66666666666 / filter_k))
        );
#endif
    // ---------------------------------------------------
#endif
}
//**********************************************-----------------------------


//**********************************************
void
PhotonMap::irrad_gradient_estimate (R3 & irrad,     // returned irradiance
                                    //				    R3 & irgrad,    // returned irradiance gradient
                                    const R3 pos,      // surface position
                                    const R3 normal,    // surface normal at pos
                                    const real max_dist,// max distance to look for photons
                                    const int nphotons) const       // number of photons to use
//**********************************************
{
    int number_read = 0;
    irrad.x = irrad.y = irrad.z = 0.0;

    NearestPhotons np;
#ifdef WIN32
    np.dist2 = (real *) malloc (sizeof (real) * (nphotons + 1));
    np.index = (const Photon **) malloc (sizeof (Photon *) * (nphotons + 1));
#else
    np.dist2 = (real *) alloca (sizeof (real) * (nphotons + 1));
    np.index = (const Photon **) alloca (sizeof (Photon *) * (nphotons + 1));
#endif

    np.pos.x = pos.x;
    np.pos.y = pos.y;
    np.pos.z = pos.z;
    np.max = nphotons;
    np.found = 0;
    np.got_heap = 0;
    np.dist2[0] = max_dist * max_dist;

    // locate the nearest photons
    locate_photons (&np, 1);

    // if less than 8 photons return
    if (np.found < 8)
        return;

    // Gradient !!!
    R3 photon_sum(0,0,0);
    int used_photons = 0 ;
    unsigned char used[MAXGATHERED];

// sum irradiance from all photons
    for (int i = 1; i <= np.found; i++) {
        const Photon *p = np.index[i];
        // try not to include drastically varying normals
        if ((fabs (normal.x - p->normal.x) <= fabs (normal.x * VARIANCE) &&
                fabs (normal.y - p->normal.y) <= fabs (normal.y * VARIANCE) &&
                fabs (normal.z - p->normal.z) <= fabs (normal.z * VARIANCE)) ||
                // normal is reversed ;) ANL!!!
                (fabs (normal.x + p->normal.x) <= fabs (normal.x * VARIANCE) &&
                 fabs (normal.y + p->normal.y) <= fabs (normal.y * VARIANCE) &&
                 fabs (normal.z + p->normal.z) <= fabs (normal.z * VARIANCE))
           ) {
#define CONE_FILTER yes
#ifdef CONE_FILTER
            const real filter_k = 1.0;
            const real scale_by =
                1.0 - sqrt (np.dist2[i]) / (filter_k * sqrt (np.dist2[0]));
            AddMult (irrad, irrad, p->power, scale_by);
#else
            Add (irrad, irrad, p->power);
#endif
            // Gradient !!!
            used_photons ++;
            Add (photon_sum, photon_sum, p->pos);
            used[i] = 1;
        } else {
            used[i] = 0;
        }
    }

    SubMult(photon_sum, photon_sum, pos, used_photons);
    Mult(photon_sum, (1.0/used_photons)/sqrt(np.dist2[0]));

    for (int i = 1; i <= np.found; i++)
        if (used[i]) {
            R3 locdir;
            Sub(locdir, np.index[i]->pos, pos);
            if ( Dot(photon_sum, locdir) <
                    -0.71*sqrt(Dot(photon_sum, photon_sum))*sqrt(Dot(locdir,locdir)) )
            {  // ANL!!!!! -  hmmm?????
//	       -0.5 * sqrt(np.dist2[0]) * sqrt(np.dist2[i]) ){
//	       -0.25 * sqrt(np.dist2[0]) *  sqrt(np.dist2[i]) ){
                goto skip_border;  //LABEL!!!
            }
        }
    irrad.x *= 2;
    irrad.y *= 2;
    irrad.z *= 2; //= Dot(photon_sum, R3(0.333,0.333,0.333));

skip_border:  // LABEL!!!

    // estimate of density
#ifdef CONE_FILTER
    const real filter_k = 1.0;   // Twice defined !!!
    const real tmp =
        (1.0f / M_PI) / (np.dist2[0] * (1.0 - 0.66666666666 / filter_k));
#else
    const real tmp = (1.0f / M_PI) / (np.dist2[0]);
#endif
    irrad.x *= tmp;
    irrad.y *= tmp;
    irrad.z *= tmp;

//   irrad.x = fabs(photon_sum.x) ; //sqrt(Dot(photon_sum, photon_sum));
//   irrad.y = fabs(photon_sum.y) ; //sqrt(Dot(photon_sum, photon_sum));
//   irrad.z = fabs(photon_sum.z) ; //sqrt(Dot(photon_sum, photon_sum));
//   printf("Grad=[%2.2lf, %2.2lf, %2.2lf]\n",
//	   photon_sum.x, photon_sum.y, photon_sum.z);
}


/* locate_photons finds the nearest photons in the
 * photon map given the parameters in np
*/
//******************************************
void
PhotonMap::locate_photons (NearestPhotons * const np, const int index) const
//******************************************
{
    const Photon *p = &photons[index];
    real dist1;

    if (index < half_stored_photons) {
        dist1 = np->pos[p->plane] - p->pos[p->plane];

        if (dist1 > 0.0) {        // if dist1 is positive search right plane
            locate_photons (np, 2 * index + 1);
            if (dist1 * dist1 < np->dist2[0])
                locate_photons (np, 2 * index);
        } else {                  // dist1 is negative search left first
            locate_photons (np, 2 * index);
            if (dist1 * dist1 < np->dist2[0])
                locate_photons (np, 2 * index + 1);
        }
    }
    // compute squared distance between current photon and np->pos

    dist1 = p->pos.x - np->pos.x;
    real dist2 = dist1 * dist1;
    dist1 = p->pos.y - np->pos.y;
    dist2 += dist1 * dist1;
    dist1 = p->pos.z - np->pos.z;
    dist2 += dist1 * dist1;

    if (dist2 < np->dist2[0]) {
        // we found a photon :) Insert it in the candidate list

        if (np->found < np->max) {
            // heap is not full; use array
            np->found++;
            np->dist2[np->found] = dist2;
            np->index[np->found] = p;
        } else {
            int j, parent;

            if (np->got_heap == 0) {       // Do we need to build the heap?
                // Build heap
                real dst2;
                const Photon *phot;
                int half_found = np->found >> 1;
                for (int k = half_found; k >= 1; k--) {
                    parent = k;
                    phot = np->index[k];
                    dst2 = np->dist2[k];
                    while (parent <= half_found) {
                        j = parent + parent;
                        if (j < np->found && np->dist2[j] < np->dist2[j + 1])
                            j++;
                        if (dst2 >= np->dist2[j])
                            break;
                        np->dist2[parent] = np->dist2[j];
                        np->index[parent] = np->index[j];
                        parent = j;
                    }
                    np->dist2[parent] = dst2;
                    np->index[parent] = phot;
                }
                np->got_heap = 1;
            }
            // insert new photon into max heap
            // delete largest element, insert new and reorder the heap

            parent = 1;
            j = 2;
            while (j <= np->found) {
                if (j < np->found && np->dist2[j] < np->dist2[j + 1])
                    j++;
                if (dist2 > np->dist2[j])
                    break;
                np->dist2[parent] = np->dist2[j];
                np->index[parent] = np->index[j];
                parent = j;
                j += j;
            }
            np->index[parent] = p;
            np->dist2[parent] = dist2;

            np->dist2[0] = np->dist2[1];
        }
    }
}


/* store puts a photon into the flat array that will form
 * the final kd-tree.
 *
 * Call this function to store a photon.
*/
//***************************
void
PhotonMap::store (const R3 power, const R3 pos, const R3 dir
#ifdef PACKARDMODE
                  , const R3 normal
#endif
                 )
//***************************
{
    if (stored_photons >= max_photons) {
        printf ("ERROR: Photon Map is full, store photon ignored !!!");
        return;
    }

    stored_photons++;
    Photon *const node = &photons[stored_photons];

    for (int i = 0; i < 3; i++) {
        node->pos[i] = pos[i];

        if (node->pos[i] < bbox_min[i])
            bbox_min[i] = node->pos[i];
        if (node->pos[i] > bbox_max[i])
            bbox_max[i] = node->pos[i];

        node->power[i] = power[i];
#ifdef PACKARDMODE
        node->normal[i] = normal[i];
#endif
    }

    int theta = int (acos (dir.z) * (256.0 / M_PI));
    if (theta > 255)
        node->theta = 255;
    else
        node->theta = (unsigned char) theta;

    int phi = int (atan2 (dir.y, dir.x) * (256.0 / (2.0 * M_PI)));
    if (phi > 255)
        node->phi = 255;
    else if (phi < 0)
        node->phi = (unsigned char) (phi + 256);
    else
        node->phi = (unsigned char) phi;
}


/* scale_photon_power is used to scale the power of all
 * photons once they have been emitted from the light
 * source. scale = 1/(#emitted photons).
 * Call this function after each light source is processed.
*/
//********************************************************
void
PhotonMap::scale_photon_power (const real scale)
//********************************************************
{
    for (int i = prev_scale; i <= stored_photons; i++) {
        photons[i].power.x *= scale;
        photons[i].power.y *= scale;
        photons[i].power.z *= scale;
    }
    prev_scale = stored_photons;
}


/* balance creates a left balanced kd-tree from the flat photon array.
 * This function should be called before the photon map
 * is used for rendering.
 */
//******************************
void
PhotonMap::balance (void)
//******************************
{
    if (stored_photons > 1) {
        // allocate two temporary arrays for the balancing procedure
        Photon **pa1 =
            (Photon **) malloc (sizeof (Photon *) * (stored_photons + 1));
        Photon **pa2 =
            (Photon **) malloc (sizeof (Photon *) * (stored_photons + 1));

        int i;
        for (i = 0; i <= stored_photons; i++)
            pa2[i] = &photons[i];

        balance_segment (pa1, pa2, 1, 1, stored_photons);
        free (pa2);

        // reorganize balanced kd-tree (make a heap)
        int d, j = 1, foo = 1;
        Photon foo_photon = photons[j];

        for (i = 1; i <= stored_photons; i++) {
            d = pa1[j] - photons;
            pa1[j] = NULL;
            if (d != foo)
                photons[j] = photons[d];
            else {
                photons[j] = foo_photon;

                if (i < stored_photons) {
                    for (; foo <= stored_photons; foo++)
                        if (pa1[foo] != NULL)
                            break;
                    foo_photon = photons[foo];
                    j = foo;
                }
                continue;
            }
            j = d;
        }
        free (pa1);
    }

    half_stored_photons = stored_photons / 2 - 1;
}


#define swap(ph,a,b) { Photon *ph2=ph[a]; ph[a]=ph[b]; ph[b]=ph2; }

// median_split splits the photon array into two separate
// pieces around the median with all photons below the
// the median in the lower half and all photons above
// than the median in the upper half. The comparison
// criteria is the axis (indicated by the axis parameter)
// (inspired by routine in "Algorithms in C++" by Sedgewick)
//*****************************************************************
void
PhotonMap::median_split (Photon ** p, const int start,  // start of photon block in array
                         const int end, // end of photon block in array
                         const int median,      // desired median number
                         const int axis)        // axis to split along
//*****************************************************************
{
    int left = start;
    int right = end;

    while (right > left) {
        const real v = p[right]->pos[axis];
        int i = left - 1;
        int j = right;
        for (;;) {
            while (p[++i]->pos[axis] < v);
            while (p[--j]->pos[axis] > v && j > left);
            if (i >= j)
                break;
            swap (p, i, j);
        }

        swap (p, i, right);
        if (i >= median)
            right = i - 1;
        if (i <= median)
            left = i + 1;
    }
}


// See "Realistic image synthesis using Photon Mapping" chapter 6
// for an explanation of this function
//****************************
void
PhotonMap::balance_segment (Photon ** pbal,
                            Photon ** porg,
                            const int index, const int start, const int end)
//****************************
{
    //--------------------
    // compute new median
    //--------------------

    int median = 1;
    while ((4 * median) <= (end - start + 1))
        median += median;

    if ((3 * median) <= (end - start + 1)) {
        median += median;
        median += start - 1;
    } else
        median = end - median + 1;

    //--------------------------
    // find axis to split along
    //--------------------------

    int axis = 2;
    if ((bbox_max.x - bbox_min.x) > (bbox_max.y - bbox_min.y) &&
            (bbox_max.x - bbox_min.x) > (bbox_max.z - bbox_min.z))
        axis = 0;
    else if ((bbox_max.y - bbox_min.y) > (bbox_max.z - bbox_min.z))
        axis = 1;

    //------------------------------------------
    // partition photon block around the median
    //------------------------------------------

    median_split (porg, start, end, median, axis);

    pbal[index] = porg[median];
    pbal[index]->plane = axis;

    //----------------------------------------------
    // recursively balance the left and right block
    //----------------------------------------------

    if (median > start) {
        // balance left segment
        if (start < median - 1) {
            const real tmp = bbox_max[axis];

//      bbox_max[axis] = pbal[index]->pos[axis];
//      balance_segment( pbal, porg, 2*index, start, median-1 );
//      bbox_max[axis] = tmp;
            switch (axis) {
            case 0:
                bbox_max.x = pbal[index]->pos.x;
                balance_segment (pbal, porg, 2 * index, start, median - 1);
                bbox_max.x = tmp;
                break;
            case 1:
                bbox_max.y = pbal[index]->pos.y;
                balance_segment (pbal, porg, 2 * index, start, median - 1);
                bbox_max.y = tmp;
                break;
            case 2:
                bbox_max.z = pbal[index]->pos.z;
                balance_segment (pbal, porg, 2 * index, start, median - 1);
                bbox_max.z = tmp;
                break;
            }

        } else {
            pbal[2 * index] = porg[start];
        }
    }

    if (median < end) {
        // balance right segment
        if (median + 1 < end) {
            const real tmp = bbox_min[axis];

//      bbox_min[axis] = pbal[index]->pos[axis];
//      balance_segment( pbal, porg, 2*index+1, median+1, end );
//      bbox_min[axis] = tmp;
            switch (axis) {
            case 0:
                bbox_min.x = pbal[index]->pos.x;
                balance_segment (pbal, porg, 2 * index + 1, median + 1, end);
                bbox_min.x = tmp;
                break;
            case 1:
                bbox_min.y = pbal[index]->pos.y;
                balance_segment (pbal, porg, 2 * index + 1, median + 1, end);
                bbox_min.y = tmp;
                break;
            case 2:
                bbox_min.z = pbal[index]->pos.z;
                balance_segment (pbal, porg, 2 * index + 1, median + 1, end);
                bbox_min.z = tmp;
                break;
            }

        } else {
            pbal[2 * index + 1] = porg[end];
        }
    }
}
