// Copyright (C) 1999-2015
// Smithsonian Astrophysical Observatory, Cambridge, MA, USA
// For conditions of distribution and use, see copyright notice in "copyright"

#include <tk.h>

#include "segment.h"
#include "fitsimage.h"

Segment::Segment(const Segment& a) : Marker(a)
{
  vertex = a.vertex;
}

Segment::Segment(Base* p, const Vector& ctr,
		 const Vector& b,
		 const char* clr, int* dsh,
		 int wth, const char* fnt, const char* txt,
		 unsigned short prop, const char* cmt,
		 const List<Tag>& tg, const List<CallBack>& cb)
  : Marker(p, ctr, 0, clr, dsh, wth, fnt, txt, prop, cmt, tg, cb)
{
  angle = 0;
  strcpy(type_, "segment");

  Vector bb = b;
  vertex.append(new Vertex(-bb[0],-bb[1]));
  vertex.append(new Vertex( bb[0], bb[1]));

  updateBBox();
}

Segment::Segment(Base* p, const List<Vertex>& v, 
		 const char* clr, int* dsh,
		 int wth, const char* fnt, const char* txt,
		 unsigned short prop, const char* cmt,
		 const List<Tag>& tg, const List<CallBack>& cb)
  : Marker(p, Vector(0,0), 0, clr, dsh, wth, fnt, txt, prop, cmt, tg, cb)
{
  // Vertex list is in ref coords
  angle = 0;
  strcpy(type_, "segment");
  vertex = v;

  // find center
  center = Vector(0,0);
  vertex.head();
  do
    center += vertex.current()->vector;
  while (vertex.next());
  center /= vertex.count();

  // vertices are relative
  vertex.head();
  do
    vertex.current()->vector *= Translate(-center) * FlipY(); // no rotation
  while (vertex.next());

  updateBBox();
}

void Segment::renderX(Drawable drawable, Coord::InternalSystem sys, RenderMode mode)
{
  GC lgc = renderXGC(mode);

  vertex.head();
  Vector v1;
  Vector v2 = fwdMap(vertex.current()->vector,sys);
  vertex.next();

  do {
    v1 = v2;
    v2 = fwdMap(vertex.current()->vector,sys);
    XDrawLine(display, drawable, lgc, v1[0], v1[1], v2[0], v2[1]);
  } while (vertex.next());
}

void Segment::renderPS(int mode)
{
  renderPSGC(mode);

  vertex.head();
  int first = 1;
  do {
    ostringstream str;
    Vector v =  fwdMap(vertex.current()->vector,Coord::CANVAS);
    if (first) {
      str << "newpath " << endl
	  << v.TkCanvasPs(parent->canvas) << " moveto" << endl << ends;
      first = 0;
    }
    else
      str << v.TkCanvasPs(parent->canvas) << " lineto" << endl << ends;

    Tcl_AppendResult(parent->interp, str.str().c_str(), NULL);
  } while (vertex.next());

  ostringstream str;
  str << "stroke" << endl << ends;
  Tcl_AppendResult(parent->interp, str.str().c_str(), NULL);
}

#ifdef MAC_OSX_TK
void Segment::renderMACOSX()
{
  renderMACOSXGC();

  vertex.head();
  Vector v1;
  Vector v2 = fwdMap(vertex.current()->vector,Coord::CANVAS);
  vertex.next();

  do {
    v1 = v2;
    v2 = fwdMap(vertex.current()->vector,Coord::CANVAS);
    macosxDrawLine(v1,v2);
  } while (vertex.next());
}
#endif

#ifdef __WIN32
void Segment::renderWIN32()
{
  renderWIN32GC();

  vertex.head();
  Vector v1;
  Vector v2 =  fwdMap(vertex.current()->vector,Coord::CANVAS);
  vertex.next();

  do {
    v1 = v2;
    v2 = fwdMap(vertex.current()->vector,Coord::CANVAS);
    win32DrawLine(v1,v2);
  } while (vertex.next());
}
#endif

// Support

void Segment::updateHandles()
{
  // generate handles
  numHandle = 4 + vertex.count();
  if (handle)
    delete [] handle;
  handle = new Vector[numHandle];

  // the first four are our control handles
  BBox bb;
  vertex.head();
  do
    bb.bound(vertex.current()->vector);
  while (vertex.next());

  Vector zz = parent->zoom();
  float r = 10/zz.length();
  bb.expand(r); // give us more room

  handle[0] = fwdMap(bb.ll,Coord::CANVAS);
  handle[1] = fwdMap(bb.lr(),Coord::CANVAS);
  handle[2] = fwdMap(bb.ur,Coord::CANVAS);
  handle[3] = fwdMap(bb.ul(),Coord::CANVAS);

  // and the rest are vertices
  int i=4;
  vertex.head();
  do
    handle[i++] = fwdMap(vertex.current()->vector,Coord::CANVAS);
  while (vertex.next());
}

void Segment::updateCoords(const Matrix& mx)
{
  Scale s(mx);
  vertex.head();
  do
    vertex.current()->vector *= s;
  while (vertex.next());
  
  Marker::updateCoords(mx);
}

void Segment::edit(const Vector& v, int h)
{
  if (h < 5) {
    Vector s1 = v * bckMatrix();
    Vector s2 = bckMap(handle[h-1],Coord::CANVAS);

    if (s1[0] != 0 && s1[1] != 0 && s2[0] != 0 && s2[1] != 0) {
      double a = fabs(s1[0]/s2[0]);
      double b = fabs(s1[1]/s2[1]);
      double s = a > b ? a : b;

      vertex.head();
      do
	vertex.current()->vector *= Scale(s);
      while (vertex.next());
    }

    updateBBox();
    doCallBack(CallBack::EDITCB);
  }
  else {
    moveVertex(v,h);

    updateBBox();
    doCallBack(CallBack::EDITCB);
    doCallBack(CallBack::MOVECB); // center can change
  }
}

void Segment::rotate(const Vector& v, int h)
{
  if (h < 5)
    Marker::rotate(v,h);
  else {
    // we need to check this here, because we are really rotating
    if (canEdit()) { 
      moveVertex(v,h);

      updateBBox();
      doCallBack(CallBack::EDITCB);
      doCallBack(CallBack::MOVECB); // center can change
    }
  }
}

void Segment::reset(const Vector& b)
{
  angle = 0;
  vertex.deleteAll();

  Vector bb = b;
  vertex.append(new Vertex(-bb[0],-bb[1]));
  vertex.append(new Vertex( bb[0], bb[1]));

  updateBBox();
}

void Segment::createVertex(int which, const Vector& v)
{
  // which segment (1 to n)
  // v is in ref coords
  Matrix mm = bckMatrix();

  int seg = which-1;
  if (seg>=0 && seg<vertex.count()) {
    Vertex* n = new Vertex(v * mm);
    vertex.insert(seg,n);

    recalcCenter();

    updateBBox();
    doCallBack(CallBack::EDITCB);
    doCallBack(CallBack::MOVECB); // center can change
  }
}

void Segment::deleteVertex(int h)
{
  if (h>4) {
    int hh = h-4-1;

    if (vertex.count() > 2) {
      Vertex* v = vertex[hh];
      if (v) {
	vertex.extractNext(v);
	delete v;

	recalcCenter();

	updateBBox();
	doCallBack(CallBack::EDITCB);
	doCallBack(CallBack::MOVECB); // center can change
      }  
    }
  }
}

int Segment::getSegment(const Vector& v)
{
  // v is in canvas coords
  Matrix mm = fwdMatrix();

  vertex.head();
  Vector v1;
  Vector v2 = vertex.current()->vector * mm;
  vertex.next();

  int ii = 1;
  do {
    v1 = v2;
    v2 = vertex.current()->vector * mm;

    Vector l1 = parent->mapFromRef(v1,Coord::CANVAS);
    Vector l2 = parent->mapFromRef(v2,Coord::CANVAS);
    double a = (l2-l1).angle();
    Matrix mx = Translate(-l1) * FlipY() * Rotate(-a); 
    Vector end = l2*mx;
    Vector vv = v*mx;
    
    if (vv[0]>0 && vv[0]<end[0] && 
	vv[1]>-parent->markerEpsilon && vv[1]<parent->markerEpsilon)
      return ii;

    ii++;
  } while (vertex.next());

  return 0;
}

void Segment::moveVertex(const Vector& v, int h)
{
  Matrix mm = bckMatrix();

  if (vertex[h-5])
    vertex.current()->vector = v * mm;

  recalcCenter();
}

void Segment::recalcCenter()
{
  // recalculate center

  Vector nc;
  vertex.head();
  do
    nc += vertex.current()->vector * Rotate(angle) * FlipY();
  while (vertex.next());
  nc /= vertex.count();

  center += nc;

  // update all vertices

  vertex.head();
  do
    vertex.current()->vector -= nc * FlipY() * Rotate(-angle);
  while (vertex.next());
}

// list

void Segment::list(ostream& str, Coord::CoordSystem sys, Coord::SkyFrame sky,
		   Coord::SkyFormat format, int conj, int strip)
{
  if (!strip) {
    FitsImage* ptr = parent->findFits(sys,center);
    listPre(str, sys, sky, ptr, strip, 1);

    Matrix mm = fwdMatrix();
    switch (sys) {
    case Coord::IMAGE:
    case Coord::PHYSICAL:
    case Coord::DETECTOR:
    case Coord::AMPLIFIER:
      {
	str << type_ << '(';
	int first=1;
	vertex.head();
	do {
	  if (!first)
	    str << ',';
	  first=0;

	  Vector v = ptr->mapFromRef(vertex.current()->vector*mm,sys);
	  str << setprecision(8) << v[0] << ',' << v[1];
	}
	while (vertex.next());
	str << ')';
      }
      break;
    default:
      if (ptr->hasWCS(sys)) {
	if (ptr->hasWCSCel(sys)) {
	  switch (format) {
	  case Coord::DEGREES:
	    {
	      str << type_ << '(';
	      int first=1;
	      vertex.head();
	      do {
		if (!first)
		  str << ',';
		first=0;

		Vector v = ptr->mapFromRef(vertex.current()->vector*mm,sys,sky);
		str << setprecision(8) << v[0] << ',' << v[1];
	      }
	      while (vertex.next());
	      str << ')';
	    }
	    break;
	  case Coord::SEXAGESIMAL:
	    {
	      char buf[64];
	      char ra[16];
	      char dec[16];

	      str << type_ << '(';
	      int first=1;
	      vertex.head();
	      do {
		if (!first)
		  str << ',';
		first=0;

		ptr->mapFromRef(vertex.current()->vector*mm,sys,sky,format,buf,64);
		string x(buf);
		istringstream wcs(x);
		wcs >> ra >> dec;
		str << ra << ',' << dec;
	      }
	      while (vertex.next());
	      str << ')';
	    }
	    break;
	  }
	}
	else {
	  str << type_ << '(';
	  int first=1;
	  vertex.head();
	  do {
	    if (!first)
	      str << ',';
	    first=0;

	    Vector v = ptr->mapFromRef(vertex.current()->vector*mm,sys);
	    str << setprecision(8) << v[0] << ',' << v[1];
	  }
	  while (vertex.next());
	  str << ')';
	}
      }
    }

    if (conj)
      str << " ||";

    listProperties(str, 0);
  }
}

void Segment::listXML(ostream& str, Coord::CoordSystem sys, Coord::SkyFrame sky, 
		      Coord::SkyFormat format)
{
  FitsImage* ptr = parent->findFits(sys,center);
  Matrix mm = fwdMatrix();
  Vector* vv = new Vector[vertex.count()];

  XMLRowInit();
  XMLRow(XMLSHAPE,type_);

  vertex.head();
  int cnt =0;
  do
    vv[cnt++] =vertex.current()->vector*mm;
  while (vertex.next());
  XMLRowPoint(ptr,sys,sky,format,vv,vertex.count());
  delete [] vv;

  XMLRowProps(ptr,sys);
  XMLRowEnd(str);
}


