/* gDiagramm.cpp */
#include <stdio.h>
#include <io.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#include <math.h>
 #include <time.h>
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "gd.h"
#include "gdfontt.h"
#include "gdfonts.h"
#include "gdfontmb.h"
#include "gdfontl.h"
#include "gdfontg.h"

#include "gDiagramm.hpp"

#define POKA 0

int ReadData(char *fname);
int ReadProject(char fname[]);
#define APPNAME   "gDiagramm"
#define VERS      "0.0.5"
#define SubVERS   ""

char *Head[]=
{ APPNAME " " VERS SubVERS " " __DATE__,
  "(c) Evgeny Kotsuba",
  NULL
};

class Diagramm diagramm;

int main (int n, char *par[])
{ int rc;


  if(n != 3)
  {  printf("%s\n%s\n\n",Head[0],Head[1]);
     printf("Usage: gDiagramm dataFile formatFile\n");
     exit(1);
  }

  rc = diagramm.Read(par[2]);
  if(rc)
  {    if(rc == 1) printf("Error reading form file %s",par[2]);
       else        printf("Bad form file %s",par[2]);
       exit(1);
  }

//  rc = ReadProject(par[2]);
  rc = ReadData(par[1]);
  if(rc)
  {    printf("Error reading datafile %s",par[1]);
       exit(1);
  }
  diagramm.PlotData();
  return 0;

#if POKA
/* Create output image, 256 by 256 pixels, true color. */
  im_out = gdImageCreateTrueColor (256, 256);
  /* First color allocated is background. */
  white = gdImageColorAllocate (im_out, 255, 255, 255);
  red = gdImageColorAllocate(im_out, 255, 0, 0);
       /* Set transparent color. */
  gdImageColorTransparent (im_out, white);

  gdImageFilledEllipse(im_out, 50, 25, 98, 48, red );

  gdImageFilledEllipse(im_out, 100, 100, 50, 50, white );

  //# if POKA
  /* Try to load demoin.png and paste part of it into the
     output image. */
  in = fopen ("demoin.png", "rb");
  if (!in)
    {
      fprintf (stderr, "Can't load source image; this demo\n");
      fprintf (stderr, "is much more impressive if demoin.png\n");
      fprintf (stderr, "is available.\n");
      im_in = 0;
    }
  else
    {
      im_in = gdImageCreateFromPng (in);
      fclose (in);
      /* Now copy, and magnify as we do so */
      gdImageCopyResized (im_out, im_in,
                         32, 32, 0, 0, 192, 192, 255, 255);
    }
  red = gdImageColorAllocate (im_out, 255, 0, 0);
  green = gdImageColorAllocate (im_out, 0, 255, 0);
  blue = gdImageColorAllocate (im_out, 0, 0, 255);
  /* Rectangle */
  gdImageLine (im_out, 16, 16, 240, 16, green);
  gdImageLine (im_out, 240, 16, 240, 240, green);
  gdImageLine (im_out, 240, 240, 16, 240, green);
  gdImageLine (im_out, 16, 240, 16, 16, green);
  /* Circle */
  gdImageArc (im_out, 128, 128, 60, 20, 0, 720, blue);
  /* Arc */
  gdImageArc (im_out, 128, 128, 40, 40, 90, 270, blue);
  /* Flood fill: doesn't do much on a continuously
     variable tone jpeg original. */
  gdImageFill (im_out, 8, 8, blue);
  /* Polygon */
  points[0].x = 64;
  points[0].y = 0;
  points[1].x = 0;
  points[1].y = 128;
  points[2].x = 128;
  points[2].y = 128;
  gdImageFilledPolygon (im_out, points, 3, green);
  /* Brush. A fairly wild example also involving a line style! */
  if (im_in)
    {
      int style[8];
      brush = gdImageCreateTrueColor (16, 16);
      gdImageCopyResized (brush, im_in,
                         0, 0, 0, 0,
                         gdImageSX (brush), gdImageSY (brush),
                         gdImageSX (im_in), gdImageSY (im_in));
      gdImageSetBrush (im_out, brush);
      /* With a style, so they won't overprint each other.
         Normally, they would, yielding a fat-brush effect. */
      style[0] = 0;
      style[1] = 0;
      style[2] = 0;
      style[3] = 0;
      style[4] = 0;
      style[5] = 0;
      style[6] = 0;
      style[7] = 1;
      gdImageSetStyle (im_out, style, 8);
      /* Draw the styled, brushed line */
      gdImageLine (im_out, 0, 255, 255, 0, gdStyledBrushed);
    }
  /* Text */
  gdImageString (im_out, gdFontGiant, 32, 32,
                (unsigned char *) "hi", red);
  gdImageStringUp (im_out, gdFontSmall, 64, 64,
                  (unsigned char *) "hi", red);
  /* Make output image interlaced (progressive, in the case of JPEG) */
  gdImageInterlace (im_out, 1);
  out = fopen ("demoout.png", "wb");
  /* Write PNG */
  gdImagePng (im_out, out);
  fclose (out);
  gdImageDestroy (im_out);
  if (im_in)
    {
      gdImageDestroy (im_in);
    }
//# e ndif //POKA

  return 0;
#endif //POKA

}

int Diagramm::PlotData(void)
{
  /* Input and output files */
  FILE *in;
  FILE *out;
  /* Input and output images */
  gdImagePtr im_in = 0, im_out = 0;
  /* Brush image */
  gdImagePtr brush;
  /* Color indexes */
  int white;
  int blue;
  int red, bred;
  int green;
  int Backgr,col, col1;
  int colAxes, color[64],i,j,ix,iy,ix1,iy1,ixold,iyold,n;
  /* gd fonts for font test */
  gdFontPtr Fonts[5];
  /* Points for polygon */
  gdPoint points[3];
  char str[80];
  double x,y, xold, x1,y1;
  int shiftX=16 ,shiftY=16;
  int styleDotted[2], styleDashed[6];

   CalcMinMax();

   printf("MinX=%f, MaxX=%f,\t",MinX,MaxX);
   printf("MinY=%f, MaxY=%f\n", MinY,MaxY);

  /* Text (non-truetype; see gdtestft for a freetype demo) */
  Fonts[0] = gdFontGetTiny ();
  Fonts[1] = gdFontGetSmall ();
  Fonts[2] = gdFontGetMediumBold ();
  Fonts[3] = gdFontGetLarge ();
  Fonts[4] = gdFontGetGiant ();
  if(TitleFont < 0 || TitleFont > 4) TitleFont = 2;
  if(AxesNumFont < 0 || AxesNumFont > 4) AxesNumFont = 1;
  shiftX = 16 + (AxesNumFont-1) * 2;
  shiftX = 16 + (AxesNumFont-1) * 2;
/* Create output image, dx by dy pixels, true color. */
  im_out = gdImageCreateTrueColor (DimX, DimY);
  /* First color allocated is background. */
  white = gdImageColorAllocate (im_out, 255, 255, 255);
  red = gdImageColorAllocate(im_out, 255, 0, 0);
  bred =gdImageColorAllocate(im_out, 160, 0, 0);

  Backgr = gdImageColorAllocate(im_out, 0xb0, 0xb0, 0xb0);
  if(BackColor != -1)
  Backgr = gdImageColorAllocate(im_out, BackColor>>16, (BackColor>>8)&0xff, BackColor&0xff);

/* fone */
  gdImageFilledRectangle(im_out, 0, 0, DimX-1,DimY-1, Backgr);
  gdImageRectangle(im_out, 0, 0, DimX-1,DimY-1, white);

/* Axes */
   colAxes = gdImageColorAllocate(im_out, 0x30, 0x30, 0x30);
   if(AxesColor == -1) AxesColor = colAxes;
   gdImageLine(im_out, shiftX- shiftX/4, shiftY, DimX-shiftX, shiftY,     AxesColor);
   gdImageLine(im_out, shiftX,(shiftY- shiftY/4), shiftX , DimY - shiftY, AxesColor);


//  gdImageStringUp (im_out, gdFontSmall, 64, 64,
//                  (unsigned char *) "hi", red);

/*
   sprintf(str,"%.f", MinX);
   gdImageString(im_out, Fonts[0], shiftX- shiftX/4, (shiftY- shiftY/8),(unsigned char*) str, bred);
   sprintf(str,"%.f", MaxX);
   gdImageString(im_out, Fonts[0],  DimX-shiftX-16, (shiftY- shiftY/8),(unsigned char*) str, bred);

   sprintf(str,"%.2f", MinY);
   gdImageStringUp(im_out, Fonts[0], 4, (shiftY+16),(unsigned char*) str, bred);
   sprintf(str,"%.2f", MaxY);
   gdImageDrawTextUp(im_out, Fonts[0], 4, (DimY - shiftY),(unsigned char*) str, bred,gdDT_LEFT|gdDT_VCENTER);
*/

/* Title*/
   gdImageDrawText(im_out, Fonts[TitleFont], DimX/2, (DimY - shiftY/4),(unsigned char*) Title, TitleColor,gdDT_CENTER|gdDT_TOP);

   mashtX = 1.;
   if(MaxX > MinX)
         mashtX = (DimX-shiftX*2) / double(MaxX - MinX);
   mashtY = 1.;
   if(MaxY > MinY)
          mashtY = (DimY-shiftY*2) / double(MaxY - MinY);

   n = (int)((MaxX-MinX)/GridXStep);
   col = gdImageColorAllocate(im_out,  GridColor>>16, (GridColor>>8)&0xff,  GridColor&0xff);
   col1 = gdImageColorAllocate(im_out,  AxesColor>>16, (AxesColor>>8)&0xff,  AxesColor&0xff);

   for(i=0;i<=n+10;i++)
   {  x = GridXStart + i*GridXStep;
      if(x < MinX) continue;
      if(x > MaxX) continue;
       x = (x - MinX)* mashtX + shiftX;
      y = shiftY;
      ix = int(x);
      iy = int(y);
      iy1 = iy - 2;
      if(!(i%5)) iy1 -= 2;
      if(!(i%10)) iy1 -= 2;
      gdImageLine(im_out, ix,iy,ix,iy1, col1);
      if(!(i%10))
      {  sprintf(str,FormatX,(GridXStart + i*GridXStep));
         gdImageDrawText(im_out, Fonts[AxesNumFont], ix, iy1+1,(unsigned char*) str, AxesNumColor,gdDT_CENTER|gdDT_TOP);

/* Set up dotted style. Leave every other pixel alone. */
        styleDotted[0] = col;
        styleDotted[1] = gdTransparent;
     /* Set up dashed style. Three on, three off. */
/*     styleDashed[0] = red;
     styleDashed[1] = red;
     styleDashed[2] = red;
     styleDashed[3] = gdTransparent;
     styleDashed[4] = gdTransparent;
     styleDashed[5] = gdTransparent;
*/
     /* Set dotted style. Note that we have to specify how many pixels are  in the style! */
         gdImageSetStyle(im_out, styleDotted, 2);
     /* Draw a line from the upper left corner to the lower right corner. */
         gdImageLine(im_out, ix, iy, ix, DimY-shiftY, gdStyled);
     /* Now the dashed line. */
//     gdImageSetStyle(im, styleDashed, 6);
//     gdImageLine(im, ix, 99, 0, 99, gdStyled);

      }
   }

   n = (int)((MaxY-MinY)/GridYStep);
   for(i=0;i<=n+10;i++)
   {  y = GridYStart + i*GridYStep;
      if(y < MinY) continue;
      if(y > MaxY) continue;
       y = ( y - MinY)* mashtY + shiftY;
      x = shiftX;
      ix = int(x);
      iy = int(y);
      ix1 = ix - 2;
      if(!(i%5)) ix1 -= 2;
      if(!(i%10)) ix1 -= 2;
      gdImageLine(im_out, ix,iy,ix1,iy, col);
      if(!(i%10))
      {  sprintf(str,FormatY,(GridYStart + i*GridYStep));
         gdImageDrawTextUp(im_out, Fonts[AxesNumFont], ix1+1, iy,(unsigned char*) str, AxesNumColor,gdDT_RIGHT|gdDT_VCENTER);
        styleDotted[0] = col;
        styleDotted[1] = gdTransparent;
         gdImageSetStyle(im_out, styleDotted, 2);
     /* Draw a line from the upper left corner to the lower right corner. */
         gdImageLine(im_out, ix, iy, DimX-shiftX, iy, gdStyled);

      }
   }


  /* Write lines */
/* Выводим данные как графики */
   for(i=0;i<nfields;i++)
   { if(!pfields[i].use) continue;
      if(i == nfieldUsedAsX) continue;
      if(pfields[i].vectorType) continue;
      color[i] = gdImageColorAllocate(im_out, rand()%256, rand()%256, rand()%256);
      if(pfields[i].color != -1)
           color[i] = gdImageColorAllocate(im_out, pfields[i].color>>16, (pfields[i].color>>8)&0xff, pfields[i].color&0xff);

     for(j=0;j<NumRec;j++)
     {
         if(nfieldUsedAsX >=0)
         {     x = mashtX * (GetValue(j,nfieldUsedAsX) - MinX);
         } else {
               x = j * mashtX;
         }
         y = mashtY * (GetValue(j,i) - MinY);
         ix = int(x);
         iy = int(y);
         if(j)
         {  if(ix != ixold || iy != iyold)
            {    if( (nfieldUsedAsX < 0) && (ix < ixold) ) ;
                 else
                     gdImageLine(im_out, ixold+shiftX,(iyold+shiftY),ix+shiftX,(iy+shiftY),  color[i]);
            }
         }
         ixold = x;
         iyold = y;
     }
   }
/******************************************/
/* Выводим данные как 2D графики */
   for(i=0;i<nfields;i++)
   {  int ix0F, iy0F, ix1F, iy1F, is;
      if(!pfields[i].use) continue;
      if(!pfields[i].vectorType) continue;
      if( ((pfields[i].vectorType-1)&0x03) != 0) continue;

      color[i] = gdImageColorAllocate(im_out, rand()%256, rand()%256, rand()%256);
      if(pfields[i].color != -1)
           color[i] = gdImageColorAllocate(im_out, pfields[i].color>>16, (pfields[i].color>>8)&0xff, pfields[i].color&0xff);
      ix0F = i;
 /* найдем дргие поля, которые соотв. 2D вектору */
      is = 0;
      for(j=0;j<nfields;j++)
      {  if(pfields[j].vectorType == pfields[i].vectorType+1)
         { is++;  iy0F = j;
           break;
         }
      }
      for(j=0;j<nfields;j++)
      {  if(pfields[j].vectorType == pfields[i].vectorType+2)
         { is++;  ix1F = j;
           break;
         }
      }
      for(j=0;j<nfields;j++)
      {  if(pfields[j].vectorType == pfields[i].vectorType+3)
         { is++;  iy1F = j;
           break;
         }
      }
      if(is != 3)
            break;
       color[iy0F] = gdImageColorAllocate(im_out, pfields[iy0F].color>>16, (pfields[iy0F].color>>8)&0xff, pfields[iy0F].color&0xff);
       color[ix1F] = gdImageColorAllocate(im_out, pfields[ix1F].color>>16, (pfields[ix1F].color>>8)&0xff, pfields[ix1F].color&0xff);
     for(j=0;j<NumRec;j++)
     {
          x  = mashtX * (GetValue(j,ix0F) - MinX);
          y  = mashtY * (GetValue(j,iy0F) - MinY);
          x1 = mashtX * (GetValue(j,ix1F) - MinX);
          y1 = mashtY * (GetValue(j,iy1F) - MinY);
         ix  = int(x);
         iy  = int(y);
         ix1 = int(x1);
         iy1 = int(y1);

         gdImageLine(im_out, ix+shiftX,(iy+shiftY),ix1+shiftX,(iy1+shiftY),  color[i]);
         gdImageSetPixel(im_out, ix+shiftX,(iy+shiftY),   color[iy0F]);
         gdImageSetPixel(im_out, ix1+shiftX,(iy1+shiftY), color[ix1F]);

     }
   }
/******************************************/
  /* Write marks */
   for(i=0;i<nfields;i++)
   { if(!pfields[i].use) continue;
     if(!pfields[i].markType) continue;
     if(i == nfieldUsedAsX) continue;
     if(pfields[i].vectorType) continue;
      color[i] = gdImageColorAllocate(im_out, rand()%256, rand()%256, rand()%256);
      if(pfields[i].markColor != -1)
           color[i] = gdImageColorAllocate(im_out, pfields[i].markColor>>16, (pfields[i].markColor>>8)&0xff, pfields[i].markColor&0xff);
     for(j=0;j<NumRec;j++)
     {
         if(nfieldUsedAsX >=0)
         {     x = mashtX * (GetValue(j,nfieldUsedAsX) - MinX);
         } else {
               x = j * mashtX;
         }

         y = mashtY * (GetValue(j,i) - MinY);
         ix = int(x);
         iy = int(y);
         if(j)
         {   PlotMark(im_out, ix+shiftX,(iy+shiftY),pfields[i].markType, color[i]);
         }
         ixold = x;
         iyold = y;
     }
   }

/* Label axes */
/* X axe */
   for(i=0;i<nfields;i++)
   { if(!pfields[i].use) continue;
      if(i == nfieldUsedAsX)
      {
         gdImageDrawText(im_out, Fonts[1], DimX-shiftX, shiftY+10,(unsigned char*) pfields[i].name,
                         bred,gdDT_RIGHT|gdDT_TOP);
         break;
      }
   }
/* Legend */
   if(LegendUse)
   {  int nf=0;
      int lx,ly;
/* count number of active fields for legend */
      for(i=0;i<nfields;i++)
      { if(!pfields[i].use) continue;
        if(i == nfieldUsedAsX) continue;
        if(pfields[i].vectorType &&
           ((pfields[i].vectorType-1)&0x03) != 0) continue;

        nf++;
      }
      if(LegendStartX == -1)
      {  LegendStartX = DimX - 30;
      }
      if(LegendStartY == -1)
      {  if(DimY > 100) LegendStartY = DimY - (nf+1) * 10;
         else           LegendStartY = int(DimY * 0.8);
      }
      nf = 0;
      for(i=0;i<nfields;i++)
      { if(!pfields[i].use) continue;
         if(i == nfieldUsedAsX) continue;
         if(pfields[i].vectorType &&
           ((pfields[i].vectorType-1)&0x03) != 0) continue;

         lx = LegendStartX;
         ly = LegendStartY+nf*10;
         nf++;
         color[0] = gdImageColorAllocate(im_out, rand()%256, rand()%256, rand()%256);
         if(pfields[i].color != -1)
            color[0] = gdImageColorAllocate(im_out, pfields[i].color>>16, (pfields[i].color>>8)&0xff, pfields[i].color&0xff);

         color[1] = gdImageColorAllocate(im_out, rand()%256, rand()%256, rand()%256);
         if(AxesNumColor != -1)
            color[1] = gdImageColorAllocate(im_out, AxesNumColor>>16, (AxesNumColor>>8)&0xff, AxesNumColor&0xff);

         gdImageLine(im_out, lx, ly ,lx+20,ly,  color[0]);
         gdImageDrawText(im_out, Fonts[1], lx-5, ly,(unsigned char*) pfields[i].name,
                                     color[1],gdDT_RIGHT|gdDT_VCENTER);
      }
   }
  /* Write PNG */
  out = fopen (FileOut, "wb");
  if(strstr(FileOut,".jpg"))
       gdImageJpeg (im_out, out,-1);
  else
       gdImagePng (im_out, out);
  fclose (out);
   return 0;
}

int Diagramm::PlotMark(gdImagePtr im, int x, int y, int type, int color)
{
   switch(type)
   {  case 1: /* pure pixel */
          gdImageSetPixel(im, x, y, color);
        break;
     case 2: /* X - like */
          gdImageLine(im, x-2, y-2, x+2,y+2,  color);
          gdImageLine(im, x-2, y+2, x+2,y-2,  color);
        break;
   }
   return 0;
}

int ReadData(char *fname)
{
  FILE *fp;
  char str[512], *pstr;
  int i,nstr=0,flen,rc;

/* Count strings */
  fp = fopen(fname,"r");
  if(fp == NULL)
     return 1;
  flen = _filelength(fileno(fp));
  do
  {  pstr = fgets(str,511,fp);
     nstr++;
  } while( !feof(fp));
  fclose(fp);
  printf("File %s, %i strings\n",fname,nstr);

  diagramm.Lendatabuf = diagramm.recordLen * (nstr+2);
  diagramm.databuff = (char *) calloc(diagramm.recordLen+1, nstr+2);
  if(diagramm.databuff == NULL)
  {  printf("Can't alloc memory %i bytes", diagramm.recordLen * nstr);
  }
  for(i=0;i<diagramm.nfields;i++)
     diagramm.strptrbuf[i] = (char *)calloc( sizeof(char),256);

  diagramm.strBuff = (char *) calloc(sizeof(char), flen+16);
  diagramm.pstrBuff = diagramm.strBuff;
/* Read data */
  fp = fopen(fname,"r");
  if(fp == NULL)
     return 2;

  for(i=0; i< nstr-10; i++)
  {  pstr = fgets(str,511,fp);
     diagramm.AddData(i,str);
  }

  for( ;i< nstr; i++)
  {  pstr = fgets(str,511,fp);
     diagramm.AddData(i,str);
  }
  fclose(fp);
  printf("\nFile %s, data was read\n",fname);
  return 0;
}

/* чтение проекта-описания файла данных */
int ReadProject(char fname[])
{  FILE *fp;
static char *s1,*s2;
  char str[256], *pstr, fmt[20];
   int i,rc,l,j,ii;

   fp = fopen(fname,"r");
   if(fp == NULL)
    {   printf("Can't open file %s",fname);
        return 1;  /* таких файлов нет */
    }
   do
   {  if(!fgets(str,255,fp)) break;
/* if it is comment or free string  - cotinue; */
      if(!strlen(str) || strchr(str,(int)';') ) continue;
        break;
    } while( !feof(fp) && !ferror(fp) );

//%i,%lf,%lf,%lf,%lf,%lf,%lf,%lf,%lf,%lf,%i,%i,%s %s %s %i:%i:%i %i
/* анализируем format string */
   l = strlen(str);
   strcpy(diagramm.FormatStr,str);

   for(i=0;i<l;i++)
   { if(str[i] != '%') continue;

     for(j=i+1;j<l;j++)
     {
        if(str[j] == 'i' || str[j] == 'd' || str[j] == 'o' || str[j] == 'x' || str[j] == 'X')
        {  diagramm.AddField(FIELDINT);
           i = j;
           break;
        }
        if(str[j] == 'u' )
        {  diagramm.AddField(FIELDUINT);
           i = j;
           break;
        }
        if(str[j] == 'e'|| str[j] == 'f' || str[j] == 'g' || str[j] == 'E' || str[j] == 'G')
        {  if(str[j-1] == 'l')
             diagramm.AddField(FIELDDOUBLE);
           else
             diagramm.AddField(FIELDFLOAT);
           i = j;
           break;
        }
        if(str[j] == 'c' )
        {  diagramm.AddField(FIELDCHAR);
           i = j;
           break;
        }
        if(str[j] == 's' )
        {  diagramm.AddField(FIELDSTRING);
           i = j;
           break;
        }
     }
   }
/* рассчитываем смещения */
   diagramm.offsets[0] = 0;
   for(i=1;i<=diagramm.nfields;i++)
   {   j = 4;
       if(diagramm.pfields[i-1].type == FIELDCHAR) j = 1;
       else
           if(diagramm.pfields[i-1].type == FIELDDOUBLE) j = sizeof(double);
       diagramm.offsets[i] = diagramm.offsets[i-1] + j;
   }
   diagramm.recordLen = diagramm.offsets[diagramm.nfields];
/* read fields to plot */
   do
   {  if(!fgets(str,255,fp)) break;
/* if it is comment or free string  - cotinue; */
      if(!strlen(str) || strchr(str,(int)';') ) continue;
        break;
    } while( !feof(fp) && !ferror(fp) );

   l = strlen(str);

   for(i=0;i<l;i++)
   { if(str[i] == ' ') continue;
     for(j=0;j+i<l;j++)
     {  fmt[j] = str[i+j];
        if(str[i+j+1] <= 32)
        {  fmt[j+1] = 0;
           sscanf(fmt,"%i",&ii);
           if(ii >=0 && ii <= diagramm.nfields)
                       diagramm.pfields[ii].use = 1;
           i += j;
           break;
        }
     }
   }

/* read field names */
   j = 0;
   do
   {  if(!fgets(str,255,fp)) break;
/* if it is comment or free string  - cotinue; */
      if(!strlen(str) || strchr(str,(int)';') ) continue;
      strcpy(diagramm.pfields[j].name,str);
      if(j >= diagramm.nfields)
              break;
   } while( !feof(fp) && !ferror(fp) );
    fclose(fp);


    return 0;//;
}

