/*
 * $Id: pointfile.cxx,v 4.5 2004/07/06 14:29:02 hut66au Exp $
 *
 * Imview, the portable image analysis application
 * http://www.cmis.csiro.au/Hugues.Talbot/imview
 * ----------------------------------------------------------
 *
 *  Imview is an attempt to provide an image display application
 *  suitable for professional image analysis. It was started in
 *  1997 and is mostly the result of the efforts of Hugues Talbot,
 *  Image Analysis Project, CSIRO Mathematical and Information
 *  Sciences, with help from others (see the CREDITS files for
 *  more information)
 *
 *  Imview is Copyrighted (C) 1997-2001 by Hugues Talbot and was
 *  supported in parts by the Australian Commonwealth Science and 
 *  Industry Research Organisation. Please see the COPYRIGHT file 
 *  for full details. Imview also includes the contributions of 
 *  many others. Please see the CREDITS file for full details.
 *
 *  This program 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 2 of the License, or
 *  (at your option) any later version.
 *  
 *  This program 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 this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
 * */

/*------------------------------------------------------------------------
 *
 * impointfile.C
 *
 * This file contains the code managing pointfiles.
 * It has gotten really messy lately because of PSL's requirement at some
 * point (not of much use these days) and because of Nick Lange's wish to
 * have a drag-to-draw procedure.
 *
 * Hugues Talbot         4 Mar 1998
 *
 * Messy stuff:
 * Hugues Talbot	22 Jun 2004
 *      
 *-----------------------------------------------------------------------*/

#include <vector>   // STL
#include <algorithm>
#include <functional>
#include <assert.h>
#include "imunistd.h"
#include "imview.hxx"
#include "pointfile.hxx"
#include "imageIO.hxx"
#include "imageViewer.hxx"
#include "annotatePoints.hxx"

extern imageViewer *mainViewer;
extern imageIO     *IOBlackBox;
extern annotatept  *annotatePointPanel;
extern bool        simplePointfile;

using std::setw;
using std::mem_fun_ref;
using std::remove_if;

string ptDummyStr; // for default string arguments

// implementation for mypoint
mypoint::mypoint(void)
    : Fl_Object(0,0,0,0,0)
{
    x_ = y_ = z_ = 0;
    Rval_ = Gval_ = Bval_ = 0;
    nbraw_ = 0; // no raw data coming from display
    radius_ = 3;
    hasAnnotation_ = false;
    annotation_.erase(); // clear the string
    stringColour_ = FL_RED; // default colour
    stringFont_ = FL_TIMES;
    stringSize_ = 10;
    stringQuadrant_ = STR_SE; // south-east
    linelength_ = STR_LINE_LN;
    hidden_ = false;
    selected_ = false;
    lsty_ = STYLE_FILLED_BW; // relevant for breaks only
    lcol_ = FL_BLACK;
}

// this is the copy constructor
mypoint::mypoint(const mypoint &p)
    : Fl_Object(p.x(),p.y(), 7, 7, 0)
{
    x_ = p.x();
    y_ = p.y();
    z_ = p.z();
    Rval_ = p.Rval();
    Gval_ = p.Gval();
    Bval_ = p.Bval();
    nbraw_ = p.nbraw();
    radius_ = p.radius();
    hasAnnotation_ = p.hasAnnotation();
    annotation_ = p.annotation();
    stringColour_ = p.stringColour();
    stringFont_ = p.stringFont();
    stringSize_ = p.stringSize();
    stringQuadrant_ = p.stringQuadrant();
    linelength_ = p.linelength();
    selected_ = p.selected();
    hidden_ = p.isHidden();
    lsty_ = p.getLSty();
    lcol_ = p.getLCol();
}
    
mypoint::mypoint(int X, int Y, int Z,
                 unsigned char Rval, unsigned char Gval, unsigned char Bval,
                 int nbraw, bool annotated, string &txt, Fl_Color col, int font,
                 int size, int length, str_quadrant qdr, bool h, int pts,
                 linestyle lsty, Fl_Color lcol)
    : Fl_Object(X, Y, 7, 7, 0)
{
    x_ = X ;
    y_ = Y;
    z_ = Z;
    Rval_ = Rval;
    Gval_ = Gval;
    Bval_ = Bval;
    nbraw_ = nbraw; // data coming from imageViewer
    

    if ( annotated ) {
	hasAnnotation_ = true;
        // convert back from inlined
        annotation_inlined(txt);
        stringColour_ = col;
        stringFont_ = font;
        stringSize_ = size;
        stringQuadrant_ = qdr;
        linelength_ = length;
        hidden_ = h;
        radius_ = pts;
        lsty_ = lsty;
        lcol_ = lcol;
    } else {
	hasAnnotation_ = false;
        annotation_.erase(); // clear the string
        stringColour_ = FL_RED; // default colour
        stringFont_ = FL_TIMES;
        stringSize_ = 10;
        stringQuadrant_ = STR_SE; // south-east
        linelength_ = STR_LINE_LN;
        hidden_ = false;
        radius_ = 3;
        lsty_ = STYLE_FILLED_BW;
        lcol_ = FL_BLACK;
    }
    // at any rate
    selected_ = false;
}

mypoint::~mypoint()
{
    dbgprintf("Point at (%d,%d) destructed\n", x(), y());
}

mypoint& mypoint::operator=(mypoint &p)
{
    if (this == &p)
        return *this ; // but do nothing more
    x_ = p.x();
    y_ = p.y();
    z_ = p.z();
    Rval_ = p.Rval();
    Gval_ = p.Gval();
    Bval_ = p.Bval();
    nbraw_ = p.nbraw(); // coming from viewer
    radius_ = p.radius();
    hasAnnotation_ = p.hasAnnotation();
    annotation_ = p.annotation();
    stringColour_ = p.stringColour();
    stringFont_ = p.stringFont();
    stringSize_ = p.stringSize();
    stringQuadrant_ = p.stringQuadrant();
    linelength_ = p.linelength();
    selected_ = p.selected();
    hidden_ = p.isHidden();
    
    
    return *this;
}

const string& mypoint::annotation_inlined(void)
{
    string::size_type  pos;

    annotation_inlined_ = annotation_;

    // the whole commend must fit in a line
    while ((pos = annotation_inlined_.find("\n")) != string::npos) {
        annotation_inlined_.replace(pos, 1, "\\n"); // careful, the `\\' here is not a typo
    }

    return annotation_inlined_;
}

void mypoint::annotation_inlined(string &in)
{
    string::size_type   pos;
    annotation_ = in;

    // re-place the \n
    while ((pos = annotation_.find("\\n")) != string::npos) { // careful, the `\\' here is not a typo
        annotation_.replace(pos, 2, "\n");
    }
    
    return;
}

// 
void mypoint::pointdefaults(const pointParams &pp)
{
    defaultParms_ = pp; // POD
    return;
}

void mypoint::draw()
{
    double zoomf = mainViewer->zoomFactor();
    int maxradius = trivmin(mainViewer->w(), mainViewer->h());
    int truex = mainViewer->xOnWindow(x_);
    int truey = mainViewer->yOnWindow(y_);
    double enlarge = ((maxradius-radius_)*zoomf)/(maxradius-radius_+zoomf);
    int trueradius = (int)(enlarge + radius_);
    double pointZoom = (double)trueradius/radius_;
        

    if (IOBlackBox->getCurrZpos() == z_) {
        //dbgprintf("Circle being drawn at position (%d,%d) "
        //          "on window with radius %d, zoomfactor=%f\n",
        //            truex, truey, trueradius, zoomf);
        // do not draw outside of the image
        fl_push_no_clip();
        fl_clip(mainViewer->x(),
                mainViewer->y(),
                mainViewer->visibleWidth(),
                mainViewer->visibleHeight());

        if (selected()) {
	    fl_begin_line();
            fl_color(FL_GREEN);
            fl_circle(truex,truey,trueradius);
            fl_color(FL_RED);
            fl_circle(truex,truey,trueradius+1);
	    fl_end_line();
        } else if (!hasAnnotation_) { // non-annotated points 
	    fl_begin_line(); 
            fl_color(FL_WHITE);
            fl_circle(truex,truey,trueradius);
            fl_color(FL_BLACK);
            fl_circle(truex,truey,trueradius+1); 
	    fl_end_line();
        } else { // draw a filled-in point
            fl_color(stringColour_);
            fl_pie(truex-trueradius, truey-trueradius, trueradius * 2, trueradius * 2, 0, 360);
        }
        
        // Print the annotation, if any
        if (annotation_.length() > 0 && !hidden_) {
            int saveFont = fl_font();
            int saveSize = fl_size();
            int xdir = 0, ydir = 0;
            int ws, wss, wmax, hs, hss, xs, ys;
            int i, j, l, nbl;
            const char *text = annotation_.c_str();
            char multitext[DFLTSTRLEN], c;
            vector<int> linebreaks;

            strncpy(multitext, text, DFLTSTRLEN);
            
            fl_font(stringFont_, (int)(stringSize_ * pointZoom + 0.5));
            fl_color(stringColour_);

            
            switch (stringQuadrant_) {
              default:
              case STR_SE:
                xdir = ydir = +1;
                xs = 0;
                ys = 1; // add height to position of string
                break;
                
              case STR_NE:
                xdir = +1;
                ydir = -1;
                xs = ys = 0; // default pos is fine
                break;

              case STR_SW:
                xdir = -1;
                ydir = +1;
                xs = -1; // move text left by it width
                ys = +1; // lower text by its height
                break;

              case STR_NW:
                xdir = ydir = -1;
                xs = -1; // move text left by its width
                ys = 0; 
                break;
            }

            // remove trailing \n's if needed (perl chop)
            l = strlen(multitext);
            while ((l > 0) && (multitext[l - 1] == '\n')) {
                multitext[l - 1] = '\0';
                l--;
            }

            // draw a line from point to text
            fl_line(truex + trueradius * xdir,
                    truey + trueradius * ydir,
                    truex + (trueradius + linelength_) * xdir,
                    truey + (trueradius + linelength_) * ydir);

            // measure dimensions of chopped string
            ws = (int)(fl_width(text)); // except ws is wrong (bug in FLTK?)
            hss = fl_height();   // height of a single line
            
            // first pass: count number of lines and max width
            i = j = nbl = wmax = 0 ;
            linebreaks.clear(); // empty vector
            linebreaks.push_back(0); // first line starts at 0
            while (1) {
                // look for line breaks
                c = multitext[j];
                if ((c == '\n') || (c == '\0')) {
                    linebreaks.push_back(j+1); // record location of first char after line break
                    multitext[j] = '\0';     // force end of string
                    wss = (int)(fl_width(multitext + linebreaks[nbl])); // measure partial string
                    nbl++;
                    if (wss > wmax)
                        wmax = wss; // measure widest string!
                    if (c == '\0')
                        break;
                }
                j++;
            }

            // total height
            hs = hss * nbl;
            
            // second pass: draw the strings
            dbgprintf("printing %d lines\n", nbl);
            for (i = 0 ; i < nbl ; i++) {
                dbgprintf("Printing: %s\n", multitext + linebreaks[i]);
                fl_draw(multitext + linebreaks[i],
                        truex + (trueradius + linelength_) * xdir + wmax * xs,
                        truey + (trueradius + linelength_) * ydir + hss * (ys + i) + (ys-1) * (hss * (nbl-1)) );
                //                                           ^^^^^^^^^^^^^^^^^^^^     ^^^^^^^^^^^^^^^^^^^^^^
                //  Terms:                                   positioning of line      offset for multiline
            }
            
            
            fl_font(saveFont,saveSize);
        }
        
        fl_pop_clip(); // pop the view clip
        fl_pop_clip(); // pop the no_clip
    }
    //else {
    //  dbgprintf("This point not in current plane\n");
    //  }
        
    return;
}

// this function is called AFTER the image rotation,
// hence width and height have been swapped already!
void mypoint::rotate90p(void)
{
    int yy;
    int imw, imh;
    int s;

    dbgprintf("Old x= %d, old y= %d\n", x_, y_);
    imw = IOBlackBox->imageWidth();
    imh = IOBlackBox->imageHeight();

    yy = imh-x_-1;
    x_ = y_;
    y_ = yy;

    s = (int)stringQuadrant_;
    
    // rotate annotation too!
    if (annotation_.length() > 0) {
        s -= 1;
        if (s < 0)
            s += STR_QUAD_MAX;
        s %= STR_QUAD_MAX;
    }

    stringQuadrant_ = (str_quadrant)s;
        
    
    dbgprintf("New x= %d, new y= %d\n", x_, y_);
    
    return;
}

// this function is called AFTER the image rotation,
// hence Y and Z have been swapped already!
void mypoint::rotate3dup(void)
{
    int yy;
    int imw, imh, imt;

    dbgprintf("Old x= %d, old y= %d, old z=%d \n", x_, y_, z_);
    imw = IOBlackBox->imageWidth();
    imh = IOBlackBox->imageHeight();
    imt = IOBlackBox->imageThickness();

    // x_ unchanged
    yy = z_;
    z_ = imh-y_-1;
    y_ = yy;

    // don't bother changing the quadrant...
    
    dbgprintf("New x= %d, new y= %d, new z=%d\n", x_, y_, z_);
    
    return;
}


void mypoint::rotate3ddown(void)
{
    int yy;
    int imw, imh, imt;

    dbgprintf("Old x= %d, old y= %d, old z=%d \n", x_, y_, z_);
    imw = IOBlackBox->imageWidth();
    imh = IOBlackBox->imageHeight();
    imt = IOBlackBox->imageThickness();

    // x_ unchanged
    yy = imt-z_-1;
    z_ = y_;
    y_ = yy;

    // don't bother changing the quadrant...
    
    dbgprintf("New x= %d, new y= %d, new z=%d\n", x_, y_, z_);
    
    return;
}

void mypoint::rotate90m(void)
{
    int xx;
    int imw;
    int s;

    dbgprintf("Old x= %d, old y= %d\n", x_, y_);
    imw = IOBlackBox->imageWidth();

    xx = imw - y_ - 1;
    y_ = x_;
    x_ = xx;

    s = (int)stringQuadrant_;
    
    // rotate annotation too!
    if (annotation_.length() > 0) {
        s += 1;
        s %= STR_QUAD_MAX;
    }

    stringQuadrant_ = (str_quadrant)s;
    
    dbgprintf("New x= %d, new y= %d\n", x_, y_);
    
    return;
}

void mypoint::flipv(void)
{
    int imw = IOBlackBox->imageWidth();
    int s;
    
    x_ = imw - x_ - 1;

    s = (int)stringQuadrant_;

    s = abs(s - 3); // try it.

    stringQuadrant_ = (str_quadrant)s;
    
    return;
}

void mypoint::fliph(void)
{
    int imh = IOBlackBox->imageHeight();
    int s;
    
    s = (int)stringQuadrant_;

    switch (stringQuadrant_) {
      case STR_NE:
      case STR_SW:
        s += 1;
        break;

      case STR_SE:
      case STR_NW:
        s -= 1;
        break;

      default:
        errprintf("Incorrect quadrant\n");
        break;
    }
    
    stringQuadrant_ = (str_quadrant)s;
    
    y_ = imh - y_ - 1;

    return;
}

// implementation for pointfile
pointfile::pointfile()
{
    PtFileName = ""; // empty string
    saveAsOldFormat = 1;  // by default we save the points in the old x11() format
    currentlinestyle_ = STYLE_FILLED_BW;
    currentlinecolour_ = FL_WHITE;
    xorvalforlines = DEFAULT_XOR_FG;
    hasChanged = false; // change detection
    dbgprintf("pointfile class initialized\n");
}

pointfile::~pointfile()
{
    dbgprintf("Pointfile desctructor called, saving to file\n");
    showlist(); // this is optional
    if (hasChanged)
        savelist(false); // making a backup, recommended
    rmAllPoints(); // this is necessary to avoid some weird event backtrack side effects from FLTK.
    dbgprintf("done\n");
}

int pointfile::setPtFileName(const char *nm)
{
    PtFileName = nm;

    return 0;
}

const char *pointfile::getPtFileName(void)
{
    if (PtFileName != "") 
        return(PtFileName.c_str()); // return the C string.
    else
        return 0;
}

const char *pointfile::getPtFileNameIfPresent(const char *imageName)
{
  static char pfpath[1024];
  char *retval = 0;

  dbgprintf("Checking for the present of a pointfile for the given image.\n");
  sprintf(pfpath, "./%s", imageName);
  strcat(pfpath, ".pf");

  // pfpath now ens sensibly in .pf
  dbgprintf("Checking for %s\n", pfpath);
  if (access(pfpath, R_OK) == 0) {
    retval = pfpath;
  }

  return retval;
}

// pb with const-correctness here
int pointfile::addPoint(int x, int y, int z,
			unsigned char Rval,
                        unsigned char Gval,
                        unsigned char Bval,
                        int           hasRaw,
			pointParams &pp)
{
    return(addPoint(x,y,z,Rval,Gval,Bval,hasRaw,
		    pp.annotated, pp.txt, pp.col, pp.font, 
		    pp.size, pp.length,
		    pp.qdr, pp.h, pp.pts,
		    pp.lsty, pp.lcol));
}

int pointfile::addPoint(int x, int y, int z,
                        unsigned char Rval,
                        unsigned char Gval,
                        unsigned char Bval,
                        int           hasRaw,
			bool          annotated,
                        string        &annotation,
                        Fl_Color      color,
                        int           font,
                        int           size,
                        int           length,
                        str_quadrant  qdr,
                        bool          hidden,
                        int           ptsize,
			linestyle     lstyle,
			Fl_Color      lcolour)
{
    int       res = 0;
    mypoint  *aPoint;
    
        
    if (annotated) // a point with annotation
        aPoint = new mypoint(x,y,z,Rval,Gval,Bval,0,
			     annotated, annotation, color, font, size,
                             length, qdr, hidden, ptsize,
			     lstyle, lcolour);
    else // normal point
        aPoint = new mypoint(x,y,z,Rval,Gval,Bval,0);

    if ((z > 0) // z can be negative to indicate a break point
        || (Rval != Gval)
        || (Rval != Bval)
        || hasRaw
        || (size > 0)) {
        // basically, the current image is 3D, colour or has comments
        saveAsOldFormat = 0; // we must use a new format to save the points
    }
    
    dbgprintf("Adding point (%d,%d,%d)=%d,%d,%d to current pointfile\n",
              x, y, z, Rval, Gval, Bval);

    pointlist.push_back(*aPoint);

    // add the point to the list of things to draw
    if (!aPoint->isBreak())
        mainViewer->add(pointlist.back());

    delete aPoint;

    hasChanged = true; // pointfile was changed
    
    return res;
}

// adds a `break' in the list of points to define a point group
// and link all the points together
void pointfile::addBreak(bool redraw, linestyle lsty, Fl_Color lcol)
{
    pointIterator pi, pii;

    // work my way backwards to find the previous break
    // or the beginning of the list
    pii = pointlist.end();
    --pii; // one position from the end

    if (!pii->isBreak()) { // no adding of a break immediately after a break
	// first draw the line
	if (redraw) {
	    // work my way backwards to find the previous break
	    // or the beginning of the list
	    pii = pointlist.end();
	    --pii; // one position from the end
	    while ((!pii->isBreak()) && (pii != pointlist.begin())) 
		--pii; // first element from the end
        
	    if (pii->isBreak())
		++pii; 
        
	    // now we are at the first point from where to draw lines
	    pi = pii++;
	    while (pii !=  pointlist.end()) {
		dbgprintf("Adding a line joining A(%d, %d) to B(%d,%d)\n",
			  pi->x(), pi->y(),
			  pii->x(), pii->y()
			  );
		linelist.push_back(imline(pi->x(),
					  pi->y(),
					  pii->x(),
					  pii->y(),
					  lsty,
					  xorvalforlines,
                                          lcol));
		mainViewer->add(linelist.back());// so that it really gets displayed
		// call the draw routine by hand if possible
		(linelist.back()).redraw();
		pi = pii++;
	    }
        
	    // do not call redrawLines();
	}
        // style and colour of the line
        pointParams myparams;
	myparams.annotated = true;
        myparams.lsty = lsty;
        myparams.lcol = lcol;
	addPoint(-1,-1,WEIRD_CONSTANT, 0, 0, 0, 0, myparams);
	dbgprintf("Adding break with style=%d, colour=%d\n",
		  (int)lsty, (int)lcol);

	showlist();

	hasChanged = true; // pointfile was changed
    }

    return;
}

// This can be an extraordinarily expensive call if there are a
// large number of lines.
// redrawns all the lines.
// Does not join point groups that do not end
// with a `break'
void pointfile::redrawLines(void)
{
    pointIterator pi, pii, lastpi;
    imline        aLine;
    bool          hasABreak = false;
    
    // remove all the existing lines
    while (!linelist.empty()) {
        mainViewer->remove(linelist.front());
        // erase the line
        //(linelist.front()).undraw(); // doesn't always work
        linelist.pop_front();
    }
    // force the redraw of the entire image.
    mainViewer->redraw_image();

    // now redraw lines between groups separated by breaks.
    lastpi = pointlist.end(); // special value: means `invalid'
    for (pi = pointlist.begin() ; pi != pointlist.end() ; ++pi) {
        if (lastpi != pointlist.end() && (!(*pi).isBreak())) {
            // now look for a `break'
            if (!hasABreak) {
                for (pii = pi ; pii != pointlist.end() ; ++pii) {
                    if ((*pii).isBreak()) {
                        hasABreak = true;
                        break;
                    }                           
                }
                // if there is no break left in the list, no need
                // to try and draw any line...
                if (pii == pointlist.end())
                    break;
            }
            if (hasABreak) {
                dbgprintf("Adding a line joining A(%d, %d) to B(%d,%d)\n",
                          (*lastpi).x(), (*lastpi).y(),
                          (*pi).x(), (*pi).y()
                    );
                linelist.push_back(imline((*lastpi).x(),
                                          (*lastpi).y(),
                                          (*pi).x(),
                                          (*pi).y(),
                                          pii->getLSty(),
                                          xorValueForLines(),
                                          pii->getLCol()));
                mainViewer->add(linelist.back()); // so that it really gets displayed
                // call the draw routine by hand if possible
                //(linelist.back()).redraw();
            }
        }
        if (!(*pi).isBreak()) {
            lastpi = pi;
        } else {
            lastpi = pointlist.end();
            hasABreak = false;
        }
    }
    mainViewer->redraw();
    
    return;
}

void pointfile::resetLinesStatus(void)
{
    // tell all the lines they've just been erased...
    lineIterator li;

    for (li = linelist.begin() ; li != linelist.end() ; ++li) {
        (*li).drawnStatus(0);
    }

    return;
}

pointIterator pointfile::findClosestPoint(int x, int y)
{
    pointIterator i, minI, retval;
    int          currZ;
    int          j, minJ = -1;
    double       dist, mindist = 1e30, zoomf;
    int          maxradius = trivmin(mainViewer->w(), mainViewer->h());
    int          cutoff;

    currZ = IOBlackBox->getCurrZpos();
    dbgprintf("Removing point on plane %d nearest to (%d,%d)\n", currZ, x, y);
    for (i = pointlist.begin(), j = 0 ; i != pointlist.end() ; ++i, ++j) {
        if ((*i).z() == currZ) {
            dist = sqrt((double)((x-(*i).x())*(x-(*i).x()) +
                        (y-(*i).y())*(y-(*i).y())));
            //dbgprintf("Point %d is suitable, distance = %f\n",
            //          j, dist);

        } else {
            dist = -1;
        }
        
        if ((dist >= 0) && (dist < mindist)) {
                mindist = dist;
                minI = i;
                minJ = j; // for debugging only 
        }
    }

    zoomf= mainViewer->zoomFactor();
    cutoff = (int)(((maxradius-PTFILE_CUTOFFD)*zoomf)/(maxradius-PTFILE_CUTOFFD+zoomf) + PTFILE_CUTOFFD);

    if ((mindist >= 0) && (mindist <= cutoff )) {
        dbgprintf("Closest point #%d :(x=%4d, y=%4d, z=%4d), distance = %f\n",
                  minJ,
                  (*minI).x(),
                  (*minI).y(),
                  (*minI).z(),
                  mindist);
        retval = minI;
    } else {
	// clutters the debug output
        //if (mindist < 0)
        //    dbgprintf("No points!\n");
        //else
        //    dbgprintf("distance to closest point too great: %f, cutoff = %d\n",
        //              mindist, cutoff);
        retval = pointlist.end();
    }

    return retval;
}

void pointfile::rmPoint(int x, int y)
{
    pointIterator minI;

    minI = findClosestPoint(x,y);
    
    if (minI != pointlist.end()) {
        // prevent it from being redrawn
        mainViewer->remove(*minI);
        // delete the appearance of the point
        mainViewer->deletePoint((*minI).x(), (*minI).y());
        // remove it from the list
        pointlist.erase(minI);
        // redraw the lines
        redrawLines();
        // pointfile was modified
        hasChanged = true;
    }
}

// remove the whole point group a point belongs to
void pointfile::rmPointGroup(int x, int y)
{
    pointIterator minI, firstI, lastI, pI;
    bool rmsome = false;
    
    dbgprintf("Before deletion:\n");
    showlist();
    minI = findClosestPoint(x,y);

    if (minI != pointlist.end()) {
        lastI = firstI = minI;
        // find the first previous break, if any
        while ((!firstI->isBreak()) && (firstI != pointlist.begin())) {
            --firstI;
        }
        while ((!lastI->isBreak()) && (lastI != pointlist.end())) {
            ++lastI;
        }
	if (firstI->isBreak())
	    ++firstI;
	
        if (lastI->isBreak()) {
            // OK, the list of points to consider is
            // surrounded by breaks;
            
            // remove all the points
            for (pI = firstI ; pI != lastI ; ++pI) {
                mainViewer->deletePoint(pI->x(), pI->y());
                mainViewer->remove(*pI);
            }
	    // don't forget to remove the break;
	    assert(lastI != pointlist.end());
	    pI++ = lastI;
            pointlist.erase(firstI, pI);
            dbgprintf("After deletion\n");
            showlist();
            rmsome=true;
        }
    }
    if (rmsome) {
        redrawLines();
        hasChanged = true;
    }

    return;
}

void pointfile::annotatePoint(int x, int y)
{
    pointIterator minI;

    minI = findClosestPoint(x,y);
    
    if (minI != pointlist.end()) {
        // specify the annotation
        string    text = annotatePointPanel->getAnnotation();
        int       colour = annotatePointPanel->getFontColour();
        int       size = annotatePointPanel->getFontSize();
        int       font = annotatePointPanel->getFont();
        str_quadrant q = annotatePointPanel->getFontQuadrant();
        int       length = annotatePointPanel->getDistance();
        bool      h = annotatePointPanel->isHidden();
        int       pts = annotatePointPanel->getPointSize();
        
        (*minI).annotation(text);
        (*minI).stringColour((Fl_Color) colour);
        (*minI).stringSize(size);
        (*minI).stringFont(font);
        (*minI).stringQuadrant(q);
        (*minI).linelength(length);
        (*minI).hide(h);
        (*minI).radius(pts);

        saveAsOldFormat = 0; // otherwise the annotation is not saved
        
        hasChanged = true;

        mainViewer->redraw();
    } 
}

// returns 1 if a point is close enough
int pointfile::getNearestPointParameters(int xx, int yy,
                                         string &ann, Fl_Color &col,
                                         int &font, int &size, int &length,
                                         str_quadrant &q, bool &h, int &ptsize)
{
    pointIterator minI;

    minI = findClosestPoint(xx,yy);

    if (minI != pointlist.end()) {
        ann = (*minI).annotation();
        col =  (*minI).stringColour();
        font = (*minI).stringFont();
        size = (*minI).stringSize();
        q =  (*minI).stringQuadrant();
        length = (*minI).linelength();
        h = (*minI).isHidden();
        ptsize = (*minI).radius();
        
        return 1;
    } else
        return 0;
}

void pointfile::rmLastPoint(void)
{
    if (!pointlist.empty()) {
        mypoint &aPoint = pointlist.back();
        dbgprintf("Removing last point from current pointfile\n");
        if (!aPoint.isBreak()) {
            // remove the last element from the list of things to draw
            mainViewer->deletePoint(aPoint.x(), aPoint.y());
            mainViewer->remove(aPoint);
        } else { 
            // we need to delete all the lines from that break
            // to the previous one or to the beginining of the list
            pointIterator pii;
            int           linesToDelete = 0;

            pii = ----pointlist.end(); // point before the one being removed
            while ((!pii->isBreak()) && (pii != pointlist.begin())) {
                --pii; // first element from the end
                ++linesToDelete;
            }
            
            // go back up one position if we found a previous break
            if (pii->isBreak()) {
                ++pii; 
                --linesToDelete;
            }
            
            // undraw the lines
            while (linesToDelete-- > 0) {
                mainViewer->remove(linelist.back());
                (linelist.back()).undraw();
                linelist.pop_back();
            }
            
        } // else nothing special to do, just remove the point.
            
        // finally remove the point from the list itself
        pointlist.pop_back();
        // do not call redrawLines();
        hasChanged = true;
    } else {
        dbgprintf("Point list is now empty\n");
    }

    return;
}




// predicate
bool mypoint::isSelectedPred(void)
{
    if (isBreak() && selected()) {
        dbgprintf("Deleting break point\n");
        return true;
    } else if (selected()) {
        dbgprintf("Deleting normal point\n");
        mainViewer->deletePoint(x(), y());
        return true;
    } else
        return false;
}

#if !defined(WIN32_NOTCYGWIN) && (defined(__GNUC__) && (__GNUC__ >= 3))
// According to Scott Meyer it's often better to use STL's algorithms
// rather than hand-written loops.
// In this case I'm not sure. Here I want to remove from the list 
// of points those that have been selected by the user. Sounds like a
// job for remove_if. The problem is that FLTK relies on the actual
// object value to draw it. The algorithm remove_if only cleverly copies objects
// inside the list, it does not really remove them as expected. As 
// a result, a simple application of remove_if wreaks havoc with FLTK's
// object list. The solution here is to remove the objects from FLTK's group list
// before touching the point list.
// The result is typical STL gobbledygook, and anyway MSVC++ 6.0 can't
// compile it...
bool pointfile::rmSelectedPoints(void)
{
    pointIterator  i;
    bool          rmsome = false;

    dbgprintf("Before deletion:\n");
    showlist();
    // remove all the points to be displayed 
    for_each(pointlist.begin(), pointlist.end(), 
             bind2nd(mem_fun_ref(&mypoint::removeself),mainViewer));
    i = remove_if(pointlist.begin(), pointlist.end(), 
                  mem_fun_ref(&mypoint::isSelectedPred));
    dbgprintf("After remove_if\n");
    showlist();
    if (i != pointlist.end()) {
        pointlist.erase(i, pointlist.end());
        dbgprintf("After erase\n");
        showlist();
        redrawLines();
        hasChanged = rmsome = true;
    }
    // add back all the points to be displayed
    for_each(pointlist.begin(), pointlist.end(), 
             bind2nd(mem_fun_ref(&mypoint::addself),mainViewer));

    return rmsome;
}

#else
// OK, so in this function we delete the points that were selected, if any
// returns true if some points were actually deleted.
// NOTE: reverse iterator were not used because they don't help:
// http://www.sgi.com/Technology/STL/ReverseIterator.html
// fundamental identity of reverse iterators:
// reverse_iterator(i).base() == i and &*ri == &*(ri.base() - 1).
// see also footnote 3 for why this is (besides making things ugly)
// This means that using ri.base() is not obvious at all.
bool pointfile::rmSelectedPoints(void)
{
    int            n;
    pointIterator  j, jj; // reverse iterator won't work properly. What a bummer.
    bool  rmsome = false;

    // We need to loop backwards because the end of
    // the list is affected by the deletion.

    dbgprintf("Before deletion:\n");
    showlist();
        
    j = pointlist.end(); // hopefully legal
    --j; // should point to the last point before the end

    n = 0;
    while (j != pointlist.end()) { // before .begin() is .end() !
        if ((*j).isBreak() && (*j).selected()) {
            // delete that break indicator as it is linked to a point
            // about to be deleted
            dbgprintf("Deleting break point\n");
            jj = j--;
            pointlist.erase(jj);
            dbgprintf("After deletion %d\n", n);
            showlist();
            n++;
        } else if ((*j).selected()) {
            dbgprintf("Deleting normal point\n");
            mainViewer->deletePoint((*j).x(), (*j).y());
            mainViewer->remove(*j);
            jj = j--;
            pointlist.erase(jj); // same page
            dbgprintf("After deletion %d\n", n);
            showlist();
            rmsome = true;
            n++;
        } else
            --j; // simply decrememnt
    }

    if (rmsome) {
        redrawLines();
        hasChanged = true;
    }

    return rmsome;
}
#endif

void pointfile::rmAllPoints(void)
{
    dbgprintf("Removing all points from current pointfile\n");
    // delete the points
    while (!pointlist.empty()) {
        mypoint &aPoint = pointlist.back();
        // remove the last element from the list of things to draw
        mainViewer->deletePoint(aPoint.x(), aPoint.y());
        mainViewer->remove(aPoint);
        // then remove it from the list itself
        pointlist.pop_back();
    }
    // delete the lines
    while (!linelist.empty()) {
        imline &aLine = linelist.back();
        // same as above
        mainViewer->remove(aLine);
        // this will just delete the objects
        linelist.pop_back();
    }
    saveAsOldFormat = 1; // until we learn better, maybe.
    hasChanged = true;
    // reset the point colour
    if (mainViewer)
        mainViewer->setPtColourIndex(0);
    
    return;
}

void pointfile::changeSelection(int x, int y, int w, int h, bool selectStatus)
{
    pointIterator i;
    lineIterator  l;
    int           x1, y1, x2, y2;
    bool         allSelected = true;

    for (i = pointlist.begin(); i != pointlist.end(); ++i) {
        // select `breaks'
        if ((*i).isBreak()) {
            if (allSelected) {
                (*i).select(selectStatus); // a break is selected if all group points are selected too.
                if (selectStatus)
                    dbgprintf("Selecting a `break'\n");
                else
                    dbgprintf("Un-selecting a `break'\n");
            }
            // in any case
            allSelected = true;
        } else {
            // select normal points
            if (((*i).x() >= x) && ((*i).x() <= (x + w))
                && ((*i).y() >= y) && ((*i).y() <= (y + h))) {
                (*i).select(selectStatus);
            } else {
                allSelected = false;
            }
        }
        
    }

    // select lines
    for (l = linelist.begin() ; l != linelist.end() ; ++l) {
        (*l).getExtremities(x1,y1,x2,y2);
        if ((x1 >= x) && (x1 <= (x + w))
            && (x2 >= x) && (x2 <= (x + w))
            && (y1 >= y) && (y1 <= (y + h))
            && (y2 >= y) && (y2 <= (y + h))) {
            // both extremities are in the selection
            (*l).select(selectStatus);
        }
                                 
    }

    return;
}

void pointfile::selectPoints(int x, int y, int w, int h, ptselectflag what)
{
    pointIterator i;
    lineIterator  l;
    int           x1, y1, x2, y2;


    dbgprintf("Selecting points in rectangle (x=%d,y=%d,w=%d,h=%d)\n",
              x, y, w, h);

    switch (what) {
        // add points to current selection
    case PT_SELECT_ADD:
        changeSelection(x,y,w,h,true);
        break;

        // remove points from selection
    case PT_SELECT_RM:
        changeSelection(x,y,w,h,false);
        break;

        // invert current selection
    case PT_SELECT_INVERT:
        for (i = pointlist.begin() ; i != pointlist.end() ; ++i) {
            if (((*i).x() >= x) && ((*i).x() <= (x + w))
                && ((*i).y() >= y) && ((*i).y() <= (y + h))) {
                (*i).select(!(*i).selected());
            }
        }
        for (l = linelist.begin() ; l != linelist.end() ; ++l) {
            (*l).getExtremities(x1,y1,x2,y2);
            if ((x1 >= x) && (x1 <= (x + w))
                && (x2 >= x) && (x2 <= (x + w))
                && (y1 >= y) && (y1 <= (y + h))
                && (y2 >= y) && (y2 <= (y + h))) {
                // both extremities are in the selection
                (*l).select(!(*l).selected());
            }
        }
        break;

    case PT_SELECT_ALL:
        for (i = pointlist.begin() ; i != pointlist.end() ; ++i) {
            (*i).select();
        }
        for (l = linelist.begin() ; l != linelist.end() ; ++l) {
            (*l).select();
        }
        break;

    case PT_SELECT_NONE:
        for (i = pointlist.begin() ; i != pointlist.end() ; ++i) {
            (*i).select(false);
        }
        for (l = linelist.begin() ; l != linelist.end() ; ++l) {
            (*l).select(false);
        }
        break;
    }

    return;    
}

void pointfile::rotate90p(void)
{
    pointIterator i;

    for (i = pointlist.begin() ; i != pointlist.end() ; ++i) {
        if (!(*i).isBreak())
            (*i).rotate90p();
    }

    redrawLines();
}

void pointfile::rotate90m(void)
{
    pointIterator i;

    for (i = pointlist.begin() ; i != pointlist.end() ; ++i) {
        if (!(*i).isBreak())
            (*i).rotate90m();
    }

    redrawLines();
}

void pointfile::rotate3dup(void)
{
    pointIterator i;

    for (i = pointlist.begin() ; i != pointlist.end() ; ++i) {
        if (!(*i).isBreak())
            (*i).rotate3dup();
    }

    redrawLines();
}

void pointfile::rotate3ddown(void)
{
    pointIterator i;

    for (i = pointlist.begin() ; i != pointlist.end() ; ++i) {
        if (!(*i).isBreak())
            (*i).rotate3ddown();
    }

    redrawLines();
}

void pointfile::fliph(void)
{
    pointIterator i;

    for (i = pointlist.begin() ; i != pointlist.end() ; ++i) {
        if (!(*i).isBreak())
            (*i).fliph();
    }

    redrawLines();
}

void pointfile::flipv(void)
{
    pointIterator i;

    for (i = pointlist.begin() ; i != pointlist.end() ; ++i) {
        if (!(*i).isBreak())
            (*i).flipv();
    }

    redrawLines();
}


void pointfile::showlist(void)
{
    pointIterator i;
    int j;
    
    dbgprintf("Printing content of pointfile\n");
    for (i = pointlist.begin(), j = 0 ; i != pointlist.end() ; ++i, ++j) {
	if (i->isBreak()) 
	    dbgprintf("%4d: Break (style=%d, col=%d), selected=%s\n",
		      j,
		      (int)(i->getLSty()),
		      (int)(i->getLCol()),
		      (*i).selected() ? "yes":"no");
	else
	    dbgprintf("%4d: (x=%4d, y=%4d, z=%4d) = %4d,%4d,%4d, selected=%s \n",
		      j,
		      (*i).x(),
		      (*i).y(),
		      (*i).z(),
		      (*i).Rval(),(*i).Gval(),(*i).Bval(),
		      (*i).selected() ? "yes":"no");
    }
    
    return;
}

// output to string
int pointfile::savelist(string &s)
{
    int retval = 0;

    if (!pointlist.empty()) {
        ostringstream ssout;
        dbgprintf("Saving the point list to a string\n");
        if (ssout) {
            retval = outputlist(ssout);
            // convert stream content to string 
            s = ssout.str();
        } else {
            errprintf("Could not open string stream\n");
            retval = 1;
        }
    } else { 
        s = "Empty\n"; // for now...
    }
    
    return retval;
}

// save to file
int pointfile::savelist(bool forsure)
{
    int retval = 0;

    if (!pointlist.empty()) {
        string trueFileName;

        if (!forsure) // backup
            trueFileName = PtFileName + ".bak";
        else
            trueFileName = PtFileName;

        ofstream PtFile(trueFileName.c_str()); ///,ios_base::out); // declare and open the file

        dbgprintf("Saving the point list to %s\n", trueFileName.c_str());
        if (PtFile) {
            retval = outputlist(PtFile);
            if ((retval == 0) && forsure) {
                // saving was successful, no need to back up until next
                // actual change
                hasChanged = false;
            }
        } else {
            errprintf("Could not open file %s\nfor saving point file: %s\n",
                      trueFileName.c_str(),
                      strerror(errno));
            retval = 1;
        }
    } else {
        dbgprintf("Point list is empty\n");
        if (forsure) {
            ofstream PtFile(PtFileName.c_str());
            if (PtFile) {
                PtFile << endl; // empty the file
                // retval stay at 0, not an error here.
            } else {
                errprintf("Could not clearfile %s: %s\n",
                      PtFileName.c_str(),
                      strerror(errno));
                retval = 1;
            }
        }
    }
    
    // stream gets closed automatically here, if opened.
    return retval;
}

// output the list to arbitrary stream
int pointfile::outputlist(ostream &sout)
{
    if (saveAsOldFormat) {
        // old format is row, column, grey-level
        for (pointIterator i = pointlist.begin() ; i != pointlist.end() ; ++i) {
            if ((*i).x() >= 0) {
                // a normal point
                sout << (*i).y() + IOBlackBox->getYOffset() << " "
                     << (*i).x() + IOBlackBox->getXOffset() << " "
                     << (*i).Rval() << "\n";
            } else {
                // a break point
                sout << "break" << "\n";
            }
        }
    } else {
        // new format, hardly a revolution
        sout << "# pointfile for " << imgName << "\n";
        sout << "# annotation parms: col fnt fntsze lnlen qdt [h]/[v] pt_radius text_of_annotation\n" ;
        sout << "#  Y    X   R    Z   G   B     x    y    z   n LBL  ...\n" ;
        for (pointIterator i = pointlist.begin() ; i != pointlist.end() ; ++i) {

            if ((*i).x() >= 0) {
                if (!simplePointfile && ((*i).annotation().length() > 0)) {
                    // output the comment first
                    sout << ANNOTEHDR << (int)((*i).stringColour()) << ' '
                         << (*i).stringFont() << ' '
                         << (*i).stringSize() << ' '
                         << (*i).linelength() << ' '
                         << (int)((*i).stringQuadrant()) << ' '
                         << (char)(((*i).isHidden()) ? 'h':'v') << ' '
                         << (*i).radius() << ' '
                         << (*i).annotation_inlined() << '\n';
                }
#ifdef INSURE_PICKY
// Phreaking Insure from hell doesn't understand setw
#    define setw(x) ' '
#endif
                sout << std::setw(4) << (*i).y() + IOBlackBox->getYOffset() << ' '
		     << std::setw(4) << (*i).x() + IOBlackBox->getXOffset() << ' '
		     << std::setw(3) << (*i).Rval() << ' ' ; // the first 3 items are the same as the old format.
                sout << std::setw(4) << (*i).z() + IOBlackBox->getZOffset() << ' '
		     << std::setw(3) << (*i).Gval() << ' '
		     << std::setw(3) << (*i).Bval() << ' ' ;
#ifdef INSURE_PICKY
#undef setw             
#endif
                sout << IOBlackBox->getRawDataInfo((*i).x(), (*i).y(), (*i).z()) << endl;
            } else {
                // a break point
                if (simplePointfile)
                    sout << "break " << endl;
                else
                    sout << "break " << i->getLSty() << " " << i->getLCol() << endl;
            }
        }
    } 

    return 0;
}

int pointfile::nbPointsInList(void)
{
    int nbp = 0;
    // returns the real number of points
    for (pointIterator i = pointlist.begin() ; i != pointlist.end() ; ++i) 
        if (!(*i).isBreak()) nbp++;

    return nbp;
}

int pointfile::nbBreaksInList(void)
{
    int nbb = 0;
    // returns the real number of breaks
    for (pointIterator i = pointlist.begin() ; i != pointlist.end() ; ++i) 
        if ((*i).isBreak()) nbb++;

    return nbb;
}

// reads and displays points belonging to a list.
void pointfile::readlist(void)
{
    dbgprintf("Reading point list from %s\n", PtFileName.c_str());

    ifstream PtFile(PtFileName.c_str());
    if (PtFile) {
        string currentLine;
        int    oldstyle;
        
        getline(PtFile,currentLine);
        if (!currentLine.empty()) {
            if (currentLine[0] == '#') {
                dbgprintf("Parsing new-style pointfile\n");
                oldstyle = 0;
                // get rid of second line as well
                getline(PtFile, currentLine);
                // Things begin to be interesting at the third line
                getline(PtFile, currentLine);
            } else {
                dbgprintf("Parsing old-style pointfile\n");
                oldstyle = 1;
            }
            while (!currentLine.empty()) {
                // because g++ 2.7.x doesn't have a recent version of the STL,
                // we shamefully revert to the C library here!
                const char *pchline = currentLine.c_str();
                int   ptx, pty, ptz;

                dbgprintf("Parsing: %s\n", pchline);
                
                if (oldstyle) {
                    int val, nbread;
                
                    nbread = sscanf(pchline, "%i %i %i", &pty, &ptx, &val);
                    if (nbread == 3) {
                        // remove the current offset, hoping it makes sense!
                        ptx -= IOBlackBox->getXOffset();
                        pty -= IOBlackBox->getYOffset();
                        ptz = 0;
                        if ((ptx >= 0) && (ptx < IOBlackBox->imageWidth())
                            && (pty >=0) && (pty < IOBlackBox->imageHeight())) {
                            // deem that a point...
                            addPoint(ptx, pty, ptz,
                                     val,
                                     val,
                                     val,
                                     0);
                        } else {
                            dbgprintf("Invalid point %d %d %d\n",
                                      ptx, pty, val);
                        }
                    } else {
                        char breakstring[100];
			int  lstyle, lcolour;
                        nbread = sscanf(pchline, "%s %d %d\n", breakstring, &lstyle, &lcolour);
                        if (strncmp(breakstring, "break", 5) == 0) {
                            // insert a break
			    if (nbread == 3) {
				addBreak(false, (linestyle)lstyle, (Fl_Color)lcolour);
			    } else
				addBreak(false); // will NOT redraw the lines
                        } else {
                            dbgprintf("Invalid line %s\n", breakstring);
                        }
                    }
                } else {
                    int  Rval, Gval, Bval;
                    int  nbread;
                    static char text[DFLTSTRLEN];
                    static int  colour, font, size = 0, length, quad, nb, ptsize;
                    static string txtstr;
                    static char   c;
                    

                    // check if annotation
                    if (pchline[0] == '#') {

                        nbread = sscanf(pchline,
                                        ANNOTEHDR "%d %d %d %d %d %c %d %n",
                                        &colour, &font, &size, &length, &quad, &c, &ptsize,
                                        &nb);
                        if (nbread >= 7) { // last %n may or may not increment nbread
                            // parse annotation
                            strncpy(text, pchline+nb, DFLTSTRLEN);
                            dbgprintf("Annotation found: %s", text);
                            txtstr = text;
                        } else {
                            size = 0;
                            getline(PtFile, currentLine);
                            continue; // just a comment, skip to next line
                        }
                    }
                    
                    nbread = sscanf(pchline, "%*i %*i %i %*i %i %i %i %i %i",
                        