/*
 * $Id: bivHist.cxx,v 4.2 2003/06/03 16:11:49 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.
 * */

/*------------------------------------------------------------------------
 *
 * This file contains the code for the bivariate histogram,
 * Both the GUI interface (with bivhist) and the code for the
 * histogram `canvas'
 *
 * Hugues Talbot	26 Apr 1999    
 *  
 *-----------------------------------------------------------------------*/

#include <stdlib.h>
#include <stdio.h> // for sprintf

#include <algorithm>

#include "imview.hxx"
#include "imageIO.hxx"
#include "imageViewer.hxx"
#include "machine.hxx"
#include "bivHist.hxx"
#include "nocase.hxx"
#include "io/newprefio.hxx"


using std::upper_bound;
using std::lower_bound;
using std::sort;


extern imageIO *IOBlackBox;
extern imageViewer *mainViewer;
extern imprefs      *fileprefs;


//
// Comparison function for points
//

// returns true if a is less than b
bool cmpBHPoints::operator() (const BHPoint &a, const BHPoint &b) const
{
    // order by Y first
    if (a.Yvalue < b.Yvalue)
	return true;
    else if (a.Yvalue > b.Yvalue)
	return false;
    // then order by X
    else if (a.Xvalue < b.Xvalue)
	return true;
    else
	return false; // if b <= a
}

//
// Implementation for bivHistBox class.
//

bivHistBox::bivHistBox(int x, int y, int w, int h, const char *l)
    : Fl_Box(x,y,w,h,l)
{
    box(FL_FLAT_BOX);
    color(FL_WHITE);
    dataIsInteger = true;
    xCorrection = yCorrection = 1.0;
    return; 
}


int bivHistBox::handle(int event)
{
    int    retval = 0;
    double xval, yval;

    if (!allPoints.empty()) {

	switch (event) {
	  case FL_ENTER:
	    fl_cursor(FL_CURSOR_CROSS);
	    retval = 1;
	    break;

	  case FL_MOVE: // no button held down
	    retval = handleMouseMoved(xval, yval); // in this case the xval and yval are thrown away
	    break;

	  case FL_PUSH:
	  case FL_DRAG:
	    retval = handleMouseButtonPushed();
	    break;

	  case FL_RELEASE:
	    //IOBlackBox->applyOverlayToCurrentImage();
	    IOBlackBox->applyImageParameters(0, 0, 0, true);
	    mainViewer->displayCurrentImage(); // this can be quite slow 
	    retval = 1;
	    break;
	    
	  case FL_LEAVE:
	    fl_cursor(FL_CURSOR_DEFAULT);
	    retval = 1;
	    break;
	    
	}
    }
    
    return retval;
}

int bivHistBox::handleMouseMoved(double &xval, double &yval)
{
    int                 Xmouse, Ymouse;
    char                p[100];   
    int                 retval = 1;
    double              realw, realh;
    
    Xmouse = Fl::event_x() - x();
    Ymouse = Fl::event_y() - y();

    realw = (double) w() - xCorrection;
    realh = (double) h() - yCorrection;
    
    xval = minXval + ((double)Xmouse * (maxXval - minXval)) / realw;
    yval = minYval + ((h() - (double)Ymouse) * (maxYval - minYval)) / realh;

    if (dataIsInteger) {
	xval = floor(xval);
	yval = floor(yval);
    }

    sprintf(p, "X= %.5g , Y=%.5g", xval, yval);
    
    parentPanel->setPosition(p);   
    
    return retval;
}

int bivHistBox::handleMouseButtonPushed(void)
{
    double              xval, yval, delta, mindelta, distance;
    int                 retval = handleMouseMoved(xval, yval);
    BHPColour           currentColour;
    bhpointIterator     l,u,p;
    BHPoint             highPoint, lowPoint;

    

    if (parentPanel->selectRegionIsOn())
	currentColour = (BHPColour)(parentPanel->getCurrentColour() + 1);
    else
	currentColour = BHPBlack;

    delta = parentPanel->getPenWidth() * (maxYval - minYval)/h() ;
    mindelta = trivmin(delta, parentPanel->getPenWidth() * (maxXval - minXval)/w() );
    
    highPoint = BHPoint(currentColour, 0, xval, yval+delta);
    lowPoint = BHPoint(currentColour, 0, xval, yval-delta);
    // and paint the point...
    u = std::upper_bound(allPoints.begin(), allPoints.end(), highPoint, cmpBHPoints());
    l = std::lower_bound(allPoints.begin(), allPoints.end(), lowPoint, cmpBHPoints());

    dbgprintf("Mouse coordinates: (%g, %g)\n", xval, yval);
    dbgprintf("Lower and upper bounds:\n");
    if (u != allPoints.end()) {
	dbgprintf("    Lower: (%g,%g)\n", (*l).Xvalue, (*l).Yvalue);
    }
    if (l != allPoints.end()) {
	dbgprintf("    Upper: (%g,%g)\n", (*u).Xvalue, (*u).Yvalue);
    }
    if ((l ==  allPoints.end()) && (u == allPoints.end())) {
	dbgprintf(" No upper or lower bounds !\n");
    }
    
    parentPanel->makeCurrent();

    fl_color((int)currentColour);
    
    for (p = l; p != u ; ++p) {
	distance = sqrt(((*p).Xvalue - xval) * ((*p).Xvalue  - xval) + ((*p).Yvalue - yval) * ((*p).Yvalue  - yval));
	if (distance <= mindelta) {
	    (*p).colour = currentColour;
	    drawPointWithJitter((*p).Xvalue, (*p).Yvalue, currentColour);
	    // this is the `linked window' bit.
	    IOBlackBox->setPixelInOverlay((*p).imgOffset, (int)currentColour);
	}
    }

    return retval;
}

void bivHistBox::draw(void)
{
    bhpointIterator  i;

    // superclass draw:
    draw_box();
    draw_label();

    // clip to scroll area dimensions
    parentPanel->getScrollClip(xo,yo,wo,ho);
    fl_clip(xo,yo,wo,ho);

    workoutCorrection();
    
    for (i = allPoints.begin() ; i != allPoints.end() ; ++i) {
	// draw with jitter
	drawPointWithJitter((*i).Xvalue, (*i).Yvalue, (*i).colour);
    }
    
    fl_pop_clip();
    return; 
}

// Work out the size of the jitter. Depends
// a lot on whether the data is integer or double.
void bivHistBox::workoutCorrection(void)
{
    if (dataIsInteger) {
	xCorrection = ((double)w()/(maxXval - minXval));
	yCorrection = ((double)h()/(maxYval - minYval));
    } else {
	xCorrection = 1.0;
	yCorrection = 1.0;
    }
}

// this assumes that the drawing environment is in place: i.e: within draw, or after make_current
// In this function, it turns out that INT_MAX is more portable than RAND_MAX.
void bivHistBox::drawPointWithJitter(double xval, double yval, BHPColour colour)
{
    int              xa, ya;
    double           realwidth,realheight;
    double           jitterX, jitterY;
    
    jitterX = (double)random()/INT_MAX * xCorrection;
    jitterY = (double)random()/INT_MAX * yCorrection;
    
    realwidth = (double)w() - xCorrection;
    realheight =  (double)h() - yCorrection ;
    
    xa = (int)(x() + realwidth*((xval-minXval)/(maxXval-minXval)) + jitterX);
    ya = (int)(y() + realheight*(1.0 - (yval-minYval)/(maxYval-minYval)) + jitterY);

    if ((xa >= xo) && (ya >= yo) && (xa < xo+wo) && (ya < yo+wo)) {
	fl_color(colour);
	fl_point(xa, ya);
    }

    return;
}

void bivHistBox::setData(int Xcomp, int Ycomp)
{
    bhpointIterator i;
    
    IOBlackBox->subsetForBH(allPoints, Xcomp, Ycomp, minXval, minYval, maxXval, maxYval);
    dbgprintf("Collected %d points, minX = %g, minY = %g\n"
	      "                     maxX = %g, maxY = %g\n",
	      allPoints.size(),
	      minXval, minYval,
	      maxXval, maxYval);

    // sample the data and determine if integer or not
    dataIsInteger = true;
    for (i = allPoints.begin(); i < allPoints.end(); i += 101) {
	if ((floor((*i).Xvalue) != (*i).Xvalue)
	    || (floor((*i).Yvalue) != (*i).Yvalue)) {
	    dataIsInteger = false;
	    break;
	}
    }
    if (dataIsInteger)
	dbgprintf("Data is integer\n");
    else
	dbgprintf("Data is floating point\n");
    
    // sort the data
	std::sort(allPoints.begin(), allPoints.end(), cmpBHPoints());
    
    return;
}

// the bivhist implementation
bivhist::bivhist()
{
    dbgprintf("Bivariate histogram dialog created\n");
    bivhistWindow=0; // fluid depend on this behaviour
    histoBox=0;
    return;
}

bivhist::~bivhist()
{
    dbgprintf("Bivariate histogram dialog destroyed\n");

    return;
}

void bivhist::show()
{
    bivhistWindow->show();
}

void bivhist::hide()
{
    bivhistWindow->hide();
}

// The coordinate change in this case is quite difficult
//
void bivhist::enlargeHisto()
{
    int x, y, w, h, x2, y2, w2, h2;
    int relx, rely, newx, newy;

    x = histoBox->x();
    y = histoBox->y();
    w = histoBox->w();
    h = histoBox->h();

    x2 = bivhistScroll->x();
    y2 = bivhistScroll->y();
    w2 = bivhistScroll->w();
    h2 = bivhistScroll->h();

    // relative coordinates
    relx = x2 - x;
    rely = y2 - y;

    newx = -2*relx-w2/2 + x2;
    newy = -2*rely-h2/2 + y2;

    dbgprintf("oldx: %d, relx: %d, newx, %d\n",
	      x, relx, newx);
    
    histoBox->resize(newx, newy, w*2, h*2);

    bivhistScroll->type(Fl_Scroll::BOTH);
    bivhistScroll->redraw();
}

// and this is just as hard
void bivhist::shrinkHisto()
{
    int x, y, w, h, x2, y2, w2, h2;
    int relx, rely, newx, newy;

    x = histoBox->x();
    y = histoBox->y();
    w = histoBox->w();
    h = histoBox->h();

    x2 = bivhistScroll->x();
    y2 = bivhistScroll->y();
    w2 = bivhistScroll->w();
    h2 = bivhistScroll->h();

    // relative coordinates
    relx = x2 - x;
    rely = y2 - y;

    newx = relx/2 + w2/4 + x2;
    newy = rely/2 + h2/4 + y2;
    
    histoBox->resize(newx, newy, w/2, h/2);
    
    bivhistScroll->type(Fl_Scroll::BOTH);
    bivhistScroll->redraw();
}

void bivhist::fitHisto()
{
    int x, y, w, h;

    x = bivhistScroll->x();
    y = bivhistScroll->y();
    w = bivhistScroll->w();
    h = bivhistScroll->h();
    
    histoBox->resize(x+BHP_MARGIN/2, y+BHP_MARGIN/2, w+BHP_MARGIN, h+BHP_MARGIN);

    bivhistScroll->type(0); // get rid of the scrollbars
    bivhistScroll->redraw();
}

// must be called first
void bivhist::setDefaults(void)
{
    // we are viewing comp. #1 vs. #2 by default.
    Xinput->value("0");
    Yinput->value("1");
    histoBox = 0; // very important
    
    return;
}

// the crux of the matter
void bivhist::computeNewBH()
{
    int    Xcomp, Ycomp;
    double minX, minY, maxX, maxY;
    // get all the elements
    Xcomp = atoi(Xinput->value());
    Ycomp = atoi(Yinput->value());
    // delete the previous canvas
    if (histoBox) {
	bivhistScroll->remove(histoBox);
	delete histoBox; // this should destroy all the data associated with histoBox
    }

    // create a new bivHistBox with the same dimensions as the current canvas
    histoBox = new bivHistBox(bivhistScroll->x()+BHP_MARGIN/2, bivhistScroll->y()+BHP_MARGIN/2,
			      bivhistScroll->w()-BHP_MARGIN, bivhistScroll->h()-BHP_MARGIN);

    histoBox->setParentPanel(this);
    bivhistScroll->add(histoBox);

    histoBox->setData(Xcomp, Ycomp);

    // set the limits
    histoBox->limits(minX, minY, maxX, maxY);
    absissaBox->setRange(minX, maxX);
    ordinateBox->setRange(minY, maxY);

    // redraw everything
    bivhistWindow->redraw();
    
    return;
}

void bivhist::setTransparency(double v)
{
    transparencyValue->value(v);
    fileprefs->overlayTransparency(v);
    mainViewer->displayCurrentImage(); // this should take the new transparency into account
}

////////// All the bivhist callbacks

void computebivhist_cb(Fl_Return_Button*, bivhist *panel)
{
    dbgprintf("Computing a new 2-D histogram\n");
    panel->computeNewBH();
}

void dismissbutton_cb(Fl_Button*, bivhist *panel)
{
    panel->hide();
}


void setpenwidth_cb(Fl_Roller *r, bivhist *panel)
{
    panel->setPenWidth(r->value());
}

void zoominbutton_cb(Fl_Button*, bivhist *panel)
{
    panel->enlargeHisto();
}

void zoomoutbutton_cb(Fl_Button*, bivhist *panel)
{
    panel->shrinkHisto();
}

void resetbutton_cb(Fl_Button*, bivhist *panel)
{
    panel->fitHisto();
}

void settransparency_cb(Fl_Roller *r, bivhist *panel)
{
    panel->setTransparency(r->value());
}
