/*

  xmunipack - fits image geometry

  Copyright © 2018 F.Hroch (hroch@physics.muni.cz)

  This file is part of Munipack.

  Munipack is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  Munipack is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with Munipack.  If not, see <http://www.gnu.org/licenses/>.

*/


#include "fits.h"
#include <cfloat>
#include <wx/wx.h>
#include <algorithm>

#ifdef __WXDEBUG__
#include <wx/debug.h>
#include <wx/txtstrm.h>
#include <wx/wfstream.h>
#endif



using namespace std;


// ------------   FitsGeometry


FitsGeometry::FitsGeometry(const FitsArray& a): FitsArray(a)
{
  wxASSERT(IsOk());
}

FitsArray FitsGeometry::Shrink(int s)
{
  wxASSERT(s >= 1 && Naxis() == 2);

  long *naxes = new long[Naxis()];
  for(int i = 0; i < Naxis(); i++)
    naxes[i] = Naxes(i)/s;
  FitsArray a = Shrink(s,naxes);
  delete[] naxes;
  return FitsArray(a);
}

FitsArray FitsGeometry::Zoom(int zoom)
{
  wxASSERT(zoom >= 1);

  long *naxes = new long[Naxis()];
  for(int i = 0; i < Naxis(); i++)
    naxes[i] = Naxes(i)*zoom;
  FitsArray a = Zoom(zoom,naxes);
  delete[] naxes;
  return FitsArray(a);
}

FitsArray FitsGeometry::Shrink(int d, const long *naxes)
{
  wxASSERT(naxes);
  /*
  const FitsArrayData *data = static_cast<FitsArrayData *>(m_refData);
  wxASSERT(data && data->array && data->naxes && data->naxis == 2);
  */

  long npixels = 1;
  for(int i = 0; i < Naxis(); i++) {
    wxASSERT(naxes[i] > 0);
    npixels = npixels*naxes[i];
  }

  float *a = new float[npixels];

  switch(Naxis()) {

  case 1:

    // mean in line
    for(int i = 0; i < naxes[0]; i++)
      *(a+i) = MeanLine(i*d,d);
    break;

  case 2:

    // mean rectangle
    for(int j = 0; j < naxes[1]; j++) {
      int y = j*d;
      float *aa = a + j*naxes[0];

      for(int i = 0; i < naxes[0]; i++) {
	int x = i*d;
	//*(aa + i) = Pixel(x,y);
	*(aa + i) = MeanRect(x,y,d,d);
      }
    }
    break;

  default:

    wxFAIL_MSG("FitsImageGeometry arbitrary dimension are not implemented yet");
    break;
  }

  long *ns = new long[Naxis()];
  for(int k = 0; k < Naxis(); k++)
    ns[k] = naxes[k];

  return FitsArray(*this,Naxis(),ns,a);
}

FitsArray FitsGeometry::Zoom(int zoom, const long *naxes)
{
  wxFAIL_MSG("FitsGeometry::Zoom reached.");
  wxASSERT(naxes);
  long npixels = 1;
  for(int i = 0; i < Naxis(); i++) {
    wxASSERT(naxes[i] > 0);
    npixels = npixels*naxes[i];
  }

  float *a = new float[npixels];

  if( Naxis() == 1 ) {
    int d = zoom;
    for(int i = 0; i < naxes[0]; i++) {
	int x = i*d;
	*(a+i) = Pixel(x);
    }
  }
  else if( Naxis() == 2 ) {

    // if( naxes[0] == Naxes(0) && naxes[1] == Naxes(1) ) {
    //   // 1:1

    //   FitsArrayData *data = static_cast<FitsArrayData *>(m_refData);
    //   wxASSERT(data);
    //   wxASSERT(data->array);

    //   int width = naxes[0];
    //   int height = naxes[1];
    //   int nbytes = width*sizeof(float);

    //   for(int i = 0; i < height; i++) {
    // 	int n = i*width;
    // 	memcpy(a+n,data->array+n,nbytes);
    //   }
    // }
    // zoom

    int dx = zoom;
    int dy = zoom;

    for(int j = 0; j < naxes[1]; j++) {
      int y = j*dy;
      float *aa = a + j*naxes[0];

      for(int i = 0; i < naxes[0]; i++) {
	int x = i*dx;
	*(aa + i) = Pixel(x,y);
      }
    }
  }
  else
    wxFAIL_MSG("FitsImageGeometry arbitrary dimension are not implemented yet");

  long *ns = new long[Naxis()];
  for(int k = 0; k < Naxis(); k++)
    ns[k] = naxes[k];

  return FitsArray(*this,Naxis(),ns,a);
}


FitsArray FitsGeometry::Scale(int wsize, int hsize)
{
  wxFAIL_MSG("FitsGeometry::Scale reached.");
  wxASSERT(IsOk());
  wxASSERT(wsize > 0 || hsize > 0);

  if( Naxis() == 1 ) {

    const int step = 9;
    int w = Width();
    int h;

    int l = (wsize*hsize)/step;

    w = wsize;
    if( hsize > 0 )
      h = max(step*(hsize/step),1);
    else
      h = max(step*(wsize/step),1);
    l = w*(h/step);

    long ns[] = {Naxes(0)};
    FitsArray image = Shrink(1,ns);

    float *a = new float[w*h];

    for(int i = 0; i < w*h; i++) a[i] = 0;

    for(int x = 0; x < l; x++) {
      int j = x/w;
      int i = x - j*w;
      float f = image.Pixel(x);
      for(int m = j*step;  m < (j + 1)*step-1; m++)
	a[m*w+i] = f;
    }
    long *nn = new long[2]; nn[0] = w; nn[1] = h;
    return FitsArray(*this,2,nn,a);

  }
  else if( Naxis() == 2 ) {

    float ratio;
    int w = Width();
    int h = Height();

    if( w > h )
      ratio = float(w)/float(wsize);
    else
      ratio = float(h)/float(hsize);

    w = max(int(w/ratio),1);
    h = max(int(h/ratio),1);

    long ns[] = {w,h};
    return Shrink(int(ratio+0.5),ns);
  }

  wxFAIL_MSG("FitsGeom::Scale implemented for 1d and 2d images only");
  return FitsArray();
}

FitsArray FitsGeometry::GetSubArray(int xoff, int yoff, int w, int h)
{
  long npixels = w*h;
  long *ns = new long[2];
  float *a = new float[npixels];
  int bytes;

  ns[0] = w;
  ns[1] = h;

  const FitsArrayData *data = static_cast<FitsArrayData *>(m_refData);
  wxASSERT(data);
  const float *array = data->array;
  wxASSERT(array);

  switch(Naxis()) {

  case 2:

    /*
    wxLogDebug("FitsGeometry::GetSubArray %d %d %d %d %d %d",xoff,yoff,w,h,
	       int(data->naxes[0]),int(data->naxes[1]));
    */
    wxASSERT(xoff >= 0 && yoff >= 0 &&
	     xoff + w <= data->naxes[0] && yoff + h <= data->naxes[1]);

    bytes = w*sizeof(float);
    for(int j = 0; j < h; j++)
      memcpy(a+j*w,array+(yoff+j)*data->naxes[0]+xoff,bytes);

    break;

  default:

    wxFAIL_MSG("FitsGeom::SubArray arbitrary dimension isn't implemented yet.");
    break;
  }

  return FitsArray(*this,2,ns,a);
}

void FitsGeometry::SetSubArray(int xoff, int yoff, const FitsArray& sub)
{
  // JUST ONLY *TWO* DIMENSIONS ARE IMPLEMENTED

  wxASSERT(sub.Naxis() == 2 && Naxis() == 2);
  FitsArrayData *data = static_cast<FitsArrayData *>(m_refData);
  wxASSERT(data);
  float *array = data->array;
  wxASSERT(array);

  /*
  wxLogDebug("FitsGeometry::SetSubArray X: %d %d %d",
	     (int)xoff,(int)sub.Naxes(0),int(data->naxes[0]));
  wxLogDebug("FitsGeometry::SetSubArray Y: %d %d %d",
	     (int)yoff,(int)sub.Naxes(1),int(data->naxes[1]));
  */
  wxASSERT(0 <= xoff && (xoff + sub.Naxes(0)) <= data->naxes[0]);
  wxASSERT(0 <= yoff && (yoff + sub.Naxes(1)) <= data->naxes[1]);

  //  int w = wxMin(sub.Naxes(0),data->naxes[0]-xoff);
  //  int h = wxMin(sub.Naxes(1),data->naxes[1]-yoff);

  int w = sub.GetWidth();
  int h = sub.GetHeight();
  const float *a = sub.PixelData();
  wxASSERT(a);
  int bytes = w*sizeof(float);

  /*
  wxLogDebug("%d %d %d %d %d %d %d %d",(int)h,(int)sub.Naxes(0),(int)w,
	     (int)sub.Naxes(1),(int)data->naxes[0],(int)xoff,
	     (int)data->naxes[1],(int)yoff);
  */

  for(int j = 0; j < h; j++)
    memcpy(array+(yoff+j)*data->naxes[0]+xoff,a+j*w,bytes);
}


float FitsGeometry::MeanLine(int x, int w)
{
  FitsArrayData *data = static_cast<FitsArrayData *>(m_refData);
  wxASSERT(data && data->array && data->naxes && data->naxis == 1);

  int i0 = x;
  int dx = w / 2;

  int i1 = max(i0 - dx,0);
  int i2 = min(long(i0 + dx),data->naxes[0]);

  float s = 0;
  float n = 0;
  for(int i = i1; i <= i2; i++) {
    // s = s + Pixel(i);
    // wxASSERT(0 <= i && i < data->npixels);
    s = s + *(data->array + i);
    n = n + 1;
  }

  if( n > 0 ) return s/n; else return 0.0;
}


float FitsGeometry::MeanRect(int x, int y, int w, int h)
{
  FitsArrayData *data = static_cast<FitsArrayData *>(m_refData);

  int i0 = x;
  int j0 = y;
  int dx = w / 2;
  int dy = h / 2;

  int i1 = max(i0 - dx,0);
  int j1 = max(j0 - dy,0);
  int i2 = min(long(i0 + dx),data->naxes[0]-1);
  int j2 = min(long(j0 + dy),data->naxes[1]-1);

  float s = 0.0;
  int n = (j2 - j1 + 1)*(i2 - i1 + 1);
  float *ai = data->array + i1;
  int idim = data->naxes[0];
  for(int j = j1; j <= j2; j++) {
    float *a = ai + j*idim;
    for(int i = i1; i <= i2; i++) {
      s += *a++;
    }
  }
  return s / n;
}

float FitsGeometry::MeanRect_debug(int x, int y, int w, int h)
{
  FitsArrayData *data = static_cast<FitsArrayData *>(m_refData);
  wxASSERT(data && data->array && data->naxes && data->naxis == 2);

  /*
  const int dmax = max(w,h)/2;

  int i0 = int(x + 0.5);
  int j0 = int(y + 0.5);
  int dx = min(int(w/2.0 - 0.5),dmax);
  int dy = min(int(h/2.0 - 0.5),dmax);

  wxASSERT(dx >= 0 && dy >= 0);

  int i1 = max(i0 - dx,0);
  int j1 = max(j0 - dy,0);
  int i2 = min(long(i0 + dx),data->naxes[0]-1);
  int j2 = min(long(j0 + dy),data->naxes[1]-1);
  */

  int i0 = x;
  int j0 = y;
  int dx = w / 2;
  int dy = h / 2;

  wxASSERT(dx >= 0 && dy >= 0);

  int i1 = max(i0 - dx,0);
  int j1 = max(j0 - dy,0);
  int i2 = min(long(i0 + dx),data->naxes[0]-1);
  int j2 = min(long(j0 + dy),data->naxes[1]-1);


  // !!!!!!!!!!!!!!!!!!!!!!!!!!!!

  //  if( dx > 0 && dy > 0 )
  //    wxLogDebug(_("%d %d"),dx,dy);

//   int ww = i2 - i1 + 1;
//   int hh = j2 - j1 + 1;
//   ww = dx;
//   hh = dy;
//   if(  ww > 0 && hh > 0 ) {
//     float a[ww*hh];
//     const float *array = data->array;
//     wxASSERT(array);
//     for(int j = 0; j < hh; j++)
//       memcpy(a+j*ww,array+(j1+j)*data->naxes[0]+i1,ww*sizeof(float));

//     //  return a[0];
//     return FitsArrayStat::QMed(ww*hh,a,max((ww*hh)/2,0));
//   }
//   else
//     return Pixel(i1,j1);


  ///!!!!!!!!!!!!!!!!!!!!!!!!!!!

  float s = 0.0;

#ifdef __WXDEBUG__
  int n = 0;
#else
  int n = (j2 - j1 + 1)*(i2 - i1 + 1);
#endif

  float *ai = data->array + i1;
  int idim = data->naxes[0];
  for(int j = j1; j <= j2; j++) {
    //    float *a = data->array + j*data->naxes[0] + i1;
    float *a = ai + j*idim;
    for(int i = i1; i <= i2; i++) {
      //      s = s + Pixel(i,j);
      //      wxASSERT(0 <= i && i < data->npixels);
      s += *a++;
      //      s = s + *(a + i);
      //      wxLogDebug("%f",(float)*(a + i));
#ifdef __WXDEBUG__
      n = n + 1;
#endif
    }
  }
#ifdef __WXDEBUG__
  int nx = j2 - j1;
  int ny = i2 - i1;
  int nn = (nx + 1)*(ny + 1);
  if( n != nn )
    wxLogDebug("FitsGeometry::MeanRect: %d %d %d %d",n,nn,nx,ny);
#endif
  wxASSERT(n == (j2 - j1 + 1)*(i2 - i1 +1) );
  if( n > 0 ) return s/n; else return 0.0;
}
