#include "../const.h"
#include <math.h>
#include <limits.h>
#include <string.h>
#include "BlockMatching.h"
#include "Image.h"
#include "MapOnExpanded.h"
#include "BMP.h"



// This function computes face motion between the two considered frames,
// comparing the localized face centroids in the two pictures and 
// the its bounding box extremes.
bool
FaceMotion(int* centroid, int* expr_centroid, int* bb, int* expr_bb, int width, int height, const Init_para& parameters, int*& movement)
{

int diff1, diff2, diff3;

#ifdef FUNC_TRACK
PRN(("Starting FaceMotion\n"));
#endif

movement = (int*) calloc(2, sizeof(int));
if (movement == NULL)
   return false;
  

// Mapping centroids (taken on the LAYER_M image) onto the expanded image
centroid[0] *= 4;
centroid[1] *= 4;
expr_centroid[0] *= 4;
expr_centroid[1] *= 4;

// Mapping 'expr_bb' onto the expanded image
MapBBOnExpanded(expr_bb);

// Computing movement on the x axis
diff1 = expr_centroid[0] - centroid[0];     // centroids difference
diff2 = expr_bb[0] - bb[0];            // bounding box extremes difference
diff3 = expr_bb[1] - bb[1];            // bounding box extremes difference
// Choosing the lowest absolute value...
movement[0] = abs(diff1) < abs(diff2) ? diff1 : diff2;
if (abs(diff3) < abs(movement[0]))
   movement[0] = diff3;
   
// Computing movement on the y axis
diff1 = expr_centroid[1] - centroid[1];     // centroids difference
diff2 = expr_bb[2] - bb[2];            // bounding box extremes difference
diff3 = expr_bb[3] - bb[3];            // bounding box extremes difference
// Choosing the lowest absolute value...
movement[1] = abs(diff1) < abs(diff2) ? diff1 : diff2;
if (abs(diff3) < abs(movement[1]))
   movement[1] = diff3;
   

// if movement is excessive, a misrecognition probably occured...
// we therefore assume face movement is non-existent...
if ((abs(movement[0]) > (width * parameters.max_shift)) || (abs(movement[1]) > (height * parameters.max_shift))) {
        movement[0] = movement[1] = 0;
}

#ifdef FUNC_TRACK
PRN(("Ending FaceMotion\n"));
#endif

return true;

}


// This function builds the search images in the 'expression' image, where each block is going to be searched for
bool
DefineSearchAreas(Block* blocks, int* movement, const Init_para& parameters, const Image& im_exp, int* face_bb, Image*& search_areas)
{

int i, w, h;
int* bounding_box;
bool result;


#ifdef FUNC_TRACK
PRN(("Starting DefineSearchAreas\n"));
#endif

bounding_box = (int*) calloc(4, sizeof(int));
if (bounding_box == NULL)
   return false;
   
search_areas = (Image*) calloc(ALL_BL_NUM, sizeof(Image));
if (search_areas == NULL)
   return false;
   
w = face_bb[1] - face_bb[0];
h = face_bb[3] - face_bb[2];

// mouth blocks search areas...
for (i = 0; i < MOUTH_BL_NUM; i++) {
      bounding_box[0] = blocks[i].x_min + movement[0];
      if (bounding_box[0] - (int)(w * parameters.add_w_mouth) >= 0)
         bounding_box[0] -= (int)(w * parameters.add_w_mouth);
      else
         bounding_box[0] = 0;

      bounding_box[1] = blocks[i].x_max + movement[0];
      if (bounding_box[1] + (int)(w * parameters.add_w_mouth) < im_exp.width)
         bounding_box[1] += (int)(w * parameters.add_w_mouth);
      else
         bounding_box[1] = im_exp.width - 1;

      bounding_box[2] = blocks[i].y_min + movement[1];
      if (bounding_box[2] - (int)(h * parameters.add_h_mouth) >= 0)
         bounding_box[2] -= (int)(h * parameters.add_h_mouth);
      else
         bounding_box[2] = 0;

      bounding_box[3] = blocks[i].y_max + movement[1];
      if (i == 4 || i == 5) {
          if (bounding_box[3] + (int)(h * parameters.add_h_mouth) < im_exp.height)
             bounding_box[3] += (int)(h * parameters.add_h_mouth);
          else
             bounding_box[3] = im_exp.height - 1;
      }

      else {
          if (bounding_box[3] + (int)(h * parameters.add_h_mouth) < im_exp.height)
             bounding_box[3] += (int)(h * parameters.add_h_mouth);
          else
             bounding_box[3] = im_exp.height - 1;
      }

      result = CropImage(bounding_box, im_exp, search_areas[i], NO_BORDER);
      if (!result)
         return false;
}

// eyebrow blocks search areas...
for (i = MOUTH_BL_NUM; i < ALL_BL_NUM; i++) {
      bounding_box[0] = blocks[i].x_min + movement[0];
      if (bounding_box[0] - (int)(w * parameters.add_w_eyebr) >= 0)
         bounding_box[0] -= (int)(w * parameters.add_w_eyebr);
      else
         bounding_box[0] = 0;

      bounding_box[1] = blocks[i].x_max + movement[0];
      if (bounding_box[1] + (int)(w * parameters.add_w_eyebr) < im_exp.width)
         bounding_box[1] += (int)(w * parameters.add_w_eyebr);
      else
         bounding_box[1] = im_exp.width - 1;

      bounding_box[2] = blocks[i].y_min + movement[1];
      if (bounding_box[2] - (int)(h * parameters.add_h_up_eyebr) >= 0)
         bounding_box[2] -= (int)(h * parameters.add_h_up_eyebr);
      else
         bounding_box[2] = 0;


      bounding_box[3] = blocks[i].y_max + movement[1];
      if (bounding_box[3] + (int)(h * parameters.add_h_down_eyebr) < im_exp.height)
         bounding_box[3] += (int)(h * parameters.add_h_down_eyebr);
      else
         bounding_box[3] = im_exp.height - 1;

      result = CropImage(bounding_box, im_exp, search_areas[i], NO_BORDER);
      if (!result)
         return false;
}


free(bounding_box);

#ifdef FUNC_TRACK
PRN(("Ending DefineSearchAreas\n"));
#endif

return true;

}

// This function determines the position of each block in the 'expression' image, and then computes their movement
bool
BlockMatching(Block* blocks, const Image& im_expanded, const Image& im_expression, const Image& im_expr_clust,
              int* face_centroid, int* face_bb, int*& face_movement, const Init_para& parameters, int counter)
{

int i, locX, locY;
int* face_expr_centroid;
int* face_expr_bb;
bool result;
Image templ, corr, im_dummy;
Block* corresponding;
Image* search_areas;

#ifdef FUNC_TRACK
PRN(("Starting BlockMatching\n"));
#endif

// Computing face motion...
result = FindFace(im_expr_clust, im_dummy, face_expr_bb, face_expr_centroid, parameters, counter);
if (!result)
   return false;
result = FaceMotion(face_centroid, face_expr_centroid, face_bb, face_expr_bb, im_expression.width, im_expression.height, parameters, face_movement);
if (!result)
   return false;


if (parameters.debug_blmatch)
   PRN(("Facemotion: (%d, %d)\n", face_movement[0], face_movement[1]));


// Defining search areas...
result = DefineSearchAreas(blocks, face_movement, parameters, im_expression, face_bb, search_areas);
if (!result)
   return false;


corresponding = (Block*) calloc(ALL_BL_NUM, sizeof(Block));
if (corresponding == NULL)
   return false;


for (i = 0; i < ALL_BL_NUM; i++)  {
    // Building template...
    result = BuildTemplate(blocks[i], im_expanded, templ);
    if (!result)
       return false;
    

    // Searching for the block in its search_area...
    result = Correlation(templ, search_areas[i], i, NCCF, corr, parameters);
    if (!result)
       return result;


    // Computing the block motion...
    result = ComputeMotion(i, blocks[i], corr, locX, locY, face_movement, search_areas[i], parameters);
    if (!result)
       return result;


    // Saving the corresponding block coordinates in the 'expression' image
    corresponding[i] = Block(locX + search_areas[i].offsetX, locX + templ.width - 1 + search_areas[i].offsetX,
                             locY + search_areas[i].offsetY, locY + templ.height - 1 + search_areas[i].offsetY);

    FreeImg(templ);
    FreeImg(corr);

}

if (parameters.debug_blmatch) {
     char name[MAX_LENGTH_FILE];
     sprintf(name, "%s%s_%d.bmp", DATA_FOLDER, parameters.file_debug_blmatch, counter);
     ShowAllBlocks(name, im_expression, corresponding);
     sprintf(name, "%s%s_of%d.bmp", DATA_FOLDER, parameters.file_debug_blmatch, counter);
     ShowOpticalFlow(name, blocks, corresponding, face_movement, im_expanded);
}

// Freeing memory...
free(corresponding);
free(face_expr_centroid);
free(face_expr_bb);
FreeImg(im_dummy);
if (search_areas != NULL) {
    for (i = 0; i < ALL_BL_NUM; i++)
       FreeImg(search_areas[i]);
    free(search_areas);
}


#ifdef FUNC_TRACK
PRN(("Ending BlockMatching\n"));
#endif

return true;

}

// This function computes the correlation between a template and a search image.
// It returns the correlation image, representing the correlation degree for each
// possible displacement of the template in the search image.
bool
Correlation(const Image& templ, const Image& search, int templ_name, int method, Image& best_corr, const Init_para& parameters)
{

int i, x, y, DeltaX, DeltaY;
double avg_t, avg_s, norm_t, norm_s, max, min, precision = 0.000000000000001;
double** corr_values;
bool result;


#ifdef FUNC_TRACK
PRN(("Starting Correlation\n"));
#endif

// Initializing...
corr_values = (double**) calloc(search.width - templ.width + 1, sizeof(double*));
if (corr_values == NULL)
   return false;
for (i = 0; i < (search.width - templ.width + 1); i++) {
    corr_values[i] = (double*) calloc(search.height - templ.height + 1, sizeof(double));
    if (corr_values[i] == NULL)
       return false;
}


// Cross-Correlation, Sum of Squared Differences, Sum of Absolute Differences methods
if (method != NCCF) {
   
   max = INT_MIN;
   min = INT_MAX;

   // Main cycle
   for (DeltaX = 0; DeltaX <= (search.width - templ.width); DeltaX++) {
           for (DeltaY = 0; DeltaY <= (search.height - templ.height); DeltaY++) {

                    for (x = 0; x < templ.width; x++) {
                         for (y = 0; y < templ.height; y++) {                             	
                            corr_values[DeltaX][DeltaY] += Similarity_function(method, templ.data[x][y], search.data[x + DeltaX][y + DeltaY]);
                        }
                    }

                    if (corr_values[DeltaX][DeltaY] > max) {
                        max = corr_values[DeltaX][DeltaY];
                    }
                    if (corr_values[DeltaX][DeltaY] < min)
                        min = corr_values[DeltaX][DeltaY];



            }
    }

}

// NCCF method
else {

    max = -1;
    min = 1;

    // Getting average greylevel of template and its norm
    Normalize(templ, templ.width, templ.height, 0, 0, avg_t, norm_t);

    
    // Main cycle
    for (DeltaX = 0; DeltaX <= (search.width - templ.width); DeltaX++) {
           for (DeltaY = 0; DeltaY <= (search.height - templ.height); DeltaY++) {
    
            // Getting average greylevel of current window and its norm
            Normalize(search, templ.width, templ.height, DeltaX, DeltaY, avg_s, norm_s);

    
            // Getting rid of regions with constant greylevel     // Da valutare...
            if (norm_s == 0.0) {
               corr_values[DeltaX][DeltaY] = -1;
            }

            else {
                 for (x = 0; x < templ.width; x++) {
                       for (y = 0; y < templ.height; y++) {
                        corr_values[DeltaX][DeltaY] += Similarity_function(method, templ.data[x][y] - avg_t, search.data[x + DeltaX][y + DeltaY] - avg_s);
                    }
                }

                corr_values[DeltaX][DeltaY] /= norm_t * norm_s;

                // Debug
                if ((fabs(corr_values[DeltaX][DeltaY]) > 1) && (fabs(corr_values[DeltaX][DeltaY] - 1.0) > precision))   // trovare limite
                   PRN(("Correlazione errata %.20f!\n", corr_values[DeltaX][DeltaY]));


                if (corr_values[DeltaX][DeltaY] > max)
                    max = corr_values[DeltaX][DeltaY];

                if (corr_values[DeltaX][DeltaY] < min)
                    min = corr_values[DeltaX][DeltaY];
            }

    
        }
    }
}

result = BuildCorrelationImage(corr_values, min, max, best_corr, search.width - templ.width + 1, search.height - templ.height + 1, parameters);


// Printing (debug only)
if (parameters.debug_corr) {
      char name[MAX_LENGTH_FILE];
      sprintf(name, "%s%s.txt", DATA_FOLDER, parameters.file_debug_corr);
      FILE* f = fopen(name, "w+");
      if (f == 0) {
         PRN(("can't open %s\n", name));
         return false;
      }
      for (DeltaY = (search.height - templ.height); DeltaY >=  0; DeltaY--) {
          for (DeltaX = 0; DeltaX <= (search.width - templ.width); DeltaX++) {
                 fprintf(f, "%lf\t", corr_values[DeltaX][DeltaY]);
          }
          fprintf(f, "\n");
      }
      fclose(f);
      // Printing correlation image
      ShowCorrelations(templ_name, corr_values, search.width - templ.width + 1, search.height - templ.height + 1, min, max, search, parameters);
}

// Freeing corr_values
for (i = 0; i < (search.width - templ.width + 1); i++)
    free(corr_values[i]);
free(corr_values);


#ifdef FUNC_TRACK
PRN(("Ending Correlation\n"));
#endif

return result;

}

// This function selects the similarity measure for correlation and compute it
// for the given values.
double
Similarity_function(int method, double p1, double p2)
{

double result;

#ifdef FUNC_TRACK
PRN(("Starting Similarity_function\n"));
#endif

if (method == CC || method == NCCF)
   result = p1 * p2;                                   // Cross Correlation
if (method == SSD)
   result = - ((p1 - p2) * (p1 - p2));                 // Sum of Squared Differences
if (method == SAD)
   result = - fabs(p1 - p2);                           // Sum of Absolute Differences


#ifdef FUNC_TRACK
PRN(("Ending Similarity_function\n"));
#endif

return result;

}

// This function returns the average graylevel in the considered portion of image and
// its norm (square root of the sum of the squares of the form (graylevel - average))
void
Normalize(const Image& image, int width, int height, int DeltaX, int DeltaY, double& avg, double& norm)
{

int x, y;

#ifdef FUNC_TRACK
PRN(("Starting Normalize\n"));
#endif

avg = 0.0;
for (x = 0; x < width; x++) {
    for (y = 0; y < height; y++) {
        avg += image.data[x + DeltaX][y + DeltaY];
    }
}

avg = avg/(width * height);

norm = 0.0;
for (x = 0; x < width; x++) {
    for (y = 0; y < height; y++) {
        norm += (image.data[x + DeltaX][y + DeltaY] - avg) * (image.data[x + DeltaX][y + DeltaY] - avg);
    }
}

norm = sqrt(norm);


#ifdef FUNC_TRACK
PRN(("Ending Normalize\n"));
#endif

return;

}

// Showing the correlation image and the most probable locations for the template
bool
ShowCorrelations(int templ_name, double** corr, int width, int height, double min, double max, const Image& search, const Init_para& parameters)
{

int x, y, min_greylevel;
char name[MAX_LENGTH_FILE], name2[MAX_LENGTH_FILE];
bool result;
BMP bmp;
Image corr_image, locations;

#ifdef FUNC_TRACK
PRN(("Starting ShowCorrelations\n"));
#endif

corr_image = Image(width, height, 0, 0);
if (corr_image.data == NULL)
   return false;

// Mapping correlation values into greylevels [0, 255]
for (x = 0; x < width; x++) {
    for (y = 0; y < height; y++) {
         corr_image.data[x][y] = (int) ((corr[x][y] - min)/(max - min) * WHITE);
      }
}

sprintf(name, "%s%s_%d_map.bmp", DATA_FOLDER, parameters.file_debug_corr, templ_name);
result = bmp.Write2File(name, corr_image);
if (!result)
   return false;

// Showing only CORREL_TH% of the pixels (locations) having the highest graylevel
min_greylevel = (int) (parameters.correl_th * WHITE);

locations = Image(search.width, search.height, 0, 0);
if (locations.data == NULL)
   return false;

for (x = 0; x < corr_image.width; x++) {
     for (y = 0; y < corr_image.height; y++) {
         if (corr_image.data[x][y] >= min_greylevel)
            locations.data[x][y] = WHITE;
      }
}

sprintf(name, "%s%s_%d_loc.bmp", DATA_FOLDER, parameters.file_debug_corr, templ_name);
result = bmp.WriteAndHighlight(name, search, locations);
if (!result)
   return false;


FreeImg(corr_image);
FreeImg(locations);


#ifdef FUNC_TRACK
PRN(("Ending ShowCorrelations\n"));
#endif

return true;

}

// This function builds the correlation image.
// A high graylevel corresponds to a high-correlation position for the template.
// Low-correlation positions (= pixels) are set to 0.
bool
BuildCorrelationImage(double** corr, double min, double max, Image& best_corr, int width, int height, const Init_para& parameters)
{

int x, y, min_greylevel, value;

#ifdef FUNC_TRACK
PRN(("Starting BuildCorrelationImage\n"));
#endif

best_corr = Image(width, height, 0, 0);
if (best_corr.data == NULL)
   return false;

min_greylevel = (int) (parameters.correl_th * WHITE);    // Showing only CORREL_TH% of the pixels (locations) having the highest graylevel

for (x = 0; x < best_corr.width; x++) {
     for (y = 0; y < best_corr.height; y++) {
         value = (int) ((corr[x][y] - min)/(max - min) * WHITE);
         if (value >= min_greylevel) {
              best_corr.data[x][y] = value;
         }
      }
}

#ifdef FUNC_TRACK
PRN(("Ending BuildCorrelationImage\n"));
#endif

return true;

}



// This function shows each block motion as an arrow pointing in the motion direction
bool
ShowOpticalFlow(char* path, Block* blocks, Block* corresponding, int* face_movement, const Image& im_exp)
{

int x, y, i, DeltaX, DeltaY;
double tan_alpha;
bool result;
Image show;
BMP bmp;

#ifdef FUNC_TRACK
PRN(("Starting ShowOpticalFlow\n"));
#endif

show = Image(im_exp.width, im_exp.height, 0, 0);
if (show.data == NULL)
   return false;

for (i = 0; i < ALL_BL_NUM; i++) {
      DeltaX = corresponding[i].x_min - blocks[i].x_min - face_movement[0];
      DeltaY = corresponding[i].y_min - blocks[i].y_min - face_movement[1];
      
      if (DeltaX == 0 && DeltaY == 0)
         show.data[blocks[i].x_min][blocks[i].y_min] = WHITE;

      else if (DeltaX == 0) {
         if (DeltaY > 0) {
            for (y = 0; y <= DeltaY; y++) {
                 show.data[blocks[i].x_min][y + blocks[i].y_min] = WHITE;
            }
         }
         else {
            for (y = 0; y >= DeltaY; y--) {
                 show.data[blocks[i].x_min][y + blocks[i].y_min] = WHITE;
            }
         }
      }
      else if (DeltaY == 0)  {
         if (DeltaX > 0) {
             for (x = 0; x <= DeltaX; x++) {
                 show.data[x + blocks[i].x_min][blocks[i].y_min] = WHITE;
             }
         }
         else {
             for (x = 0; x >= DeltaX; x--) {
                 show.data[x + blocks[i].x_min][blocks[i].y_min] = WHITE;
             }
         }

      }
      else {
          tan_alpha = (double)DeltaY/(double)DeltaX;
    
          if (DeltaX > 0) {
              for (x = 0; x <= DeltaX; x++) {
                 y = (int) (x * tan_alpha);
                 show.data[x + blocks[i].x_min][y + blocks[i].y_min] = WHITE;
              }
          }
          else {
              for (x = 0; x >= DeltaX; x--) {
                 y = (int) (x * tan_alpha);
                 show.data[x + blocks[i].x_min][y + blocks[i].y_min] = WHITE;
              }
          }

          if (DeltaY > 0){
              for (y = 0; y <= DeltaY; y++) {
                 x = (int) (y / tan_alpha);
                 show.data[x + blocks[i].x_min][y + blocks[i].y_min] = WHITE;
              }
          }
          else {
              for (y = 0; y >= DeltaY; y--) {
                 x = (int) (y / tan_alpha);
                 show.data[x + blocks[i].x_min][y + blocks[i].y_min] = WHITE;
              }
          }

      }
      // Marking the optical flow direction
      show.data[corresponding[i].x_min + 2 - face_movement[0]][corresponding[i].y_min - face_movement[1]] = WHITE;
      show.data[corresponding[i].x_min + 1 - face_movement[0]][corresponding[i].y_min - face_movement[1]] = WHITE;
      show.data[corresponding[i].x_min - 1 - face_movement[0]][corresponding[i].y_min - face_movement[1]] = WHITE;
      show.data[corresponding[i].x_min - 2 - face_movement[0]][corresponding[i].y_min - face_movement[1]] = WHITE;
      show.data[corresponding[i].x_min - face_movement[0]][corresponding[i].y_min + 2 - face_movement[1]] = WHITE;
      show.data[corresponding[i].x_min - face_movement[0]][corresponding[i].y_min + 1 - face_movement[1]] = WHITE;
      show.data[corresponding[i].x_min - face_movement[0]][corresponding[i].y_min + 1 - face_movement[1]] = WHITE;
      show.data[corresponding[i].x_min - face_movement[0]][corresponding[i].y_min - 2 - face_movement[1]] = WHITE;
}

result = bmp.WriteAndHighlight(path, im_exp, show);

FreeImg(show);

#ifdef FUNC_TRACK
PRN(("Ending ShowOpticalFlow\n"));
#endif

return result;

}

// We compute a block motion, choosing the block final position through a weighted mean of high-correlation locations
bool
ComputeMotion(int name, Block& from, const Image& corr, int& to_x, int& to_y, int* movement, const Image& search, const Init_para& parameters)
{

int DeltaX, DeltaY, module, number, i, area, total_area;
double degrees; 
bool result;

int* labels;
int* centroid;
char* dir;

Image component, im_label;


#ifdef FUNC_TRACK
PRN(("Starting ComputeMotion\n"));
#endif

// Initializing...
dir = (char*) calloc(2, sizeof(char));
to_x = to_y = 0;
total_area = 0;

// Considering clusters of adjacent high-correlation locations
result = LabelComponents(corr, im_label, number, labels);
if (!result)
   return false;


// For each cluster, we compute its weighted (with graylevels) centroid
// and its area. Then the final position is found through a weighted (with areas)
// mean of centroids.
for (i = 0; i < number; i++) {
     GetComponentGL(im_label, corr, component, labels[i]);
     area = Area(component);
     total_area += area;
     WeightedCentroid(component, centroid);
     to_x += centroid[0] * area;
     to_y += centroid[1] * area;
     FreeImg(component);
     free(centroid);

}                             

if (!total_area)
   return false;   


to_x /= (total_area);
to_y /= (total_area);

// Freeing memory...
FreeImg(im_label);
free(labels);

// Computing movement (also considering the face movement between the 2 frames)
DeltaX = (to_x + search.offsetX) - from.x_min - movement[0];
DeltaY = (to_y + search.offsetY) - from.y_min - movement[1];


DeltaY *= -1;                    //  A negative DeltaY represents, in our reference system, 
                                 //  motion towards the upper part of image

module = (int) sqrt((DeltaX * DeltaX) + (DeltaY * DeltaY));

if (!module) {
   if (parameters.debug_blmatch)
      PRN(("Block #%d: no motion.\n", name));
   SetMotion(from, 0, 0.0);
}
else {

     degrees = atan2(DeltaY, DeltaX) * 180 / PI;
     if (!ceilf(degrees) && DeltaX < 0)
        degrees = 180.0;

     SetMotion(from, module, degrees);

     if (parameters.debug_blmatch) {
           switch(from.motion_dir) {
                 case DIR_N:
                         dir = "N";
                         break;
                 case DIR_S:
                         dir = "S";
                         break;
                 case DIR_E:
                         dir = "E";
                         break;
                 case DIR_W:
                         dir = "W";
                         break;
                 case DIR_NE:
                         dir = "NE";
                         break;
                 case DIR_NW:
                         dir = "NW";
                         break;
                 case DIR_SE:
                         dir = "SE";
                         break;
                 case DIR_SW:
                         dir = "SW";
                         break;
                 default:
                         break;
      
           }
      
           PRN(("Block #%d: motion module: %d, direction %s (%.1lf degrees).\n", name, module, dir, degrees));
     }

}

#ifdef FUNC_TRACK
PRN(("Ending ComputeMotion\n"));
#endif

return true;

}



