//<copyright>
//
// Copyright (c) 1994
// Institute for Information Processing and Computer Supported New Media (IICM),
// Graz University of Technology, Austria.
//
//</copyright>


//<file>
//
// Name:       colselect.C
//
// Purpose:    Implementation of class ColorSelector
//
// Created:     3 May 94    Michael Pichler
//
// Modified:   20 May 94    Michael Pichler
//
//



#include "colselect.h"

#include "ocolor.h"

#include <InterViews/brush.h>
#include <InterViews/canvas.h>
#include <InterViews/color.h>
#include <InterViews/display.h>
#include <InterViews/event.h>
#include <InterViews/session.h>

#include <X11/keysym.h>

#include <iostream.h>


// static: colour table

const int ColorSelector::colortable_ [NUMROWS][NUMCOLUMNS][3] =
{
#include "harmap.inc"
};


/* colorDistance */
// compute distance between two colours

static const float colorEpsilon = 0.00001;  // square of euclidean metric
// minimum distance for distinguishable colours

static float colorDistance (Display* dis, float r1, float g1, float b1, const Color* color)
{
  float r2, g2, b2;
  color->intensities (dis, r2, g2, b2);

// cerr << "d([" << r1 << "," << g1 << "," << b1 << "],["
//               << r2 << "," << g2 << "," << b2 << "])=";

  r1 -= r2;
  g1 -= g2;
  b1 -= b2;

  float dist = r1 * r1 + g1 * g1 + b1 * b1;  // square of euclidean metric
//cerr << dist;
  return dist;

  // return  fabs (r1) + fabs (r2) + fabs (r3);  // manhattan metric
}


/* ColorSelector */
 
ColorSelector::ColorSelector (OColor* oc, Style* style)
: InputHandler (nil, style)
{
  oc_ = oc;
  oc->attach (this);  // listen to the observable OColor
  
  matchrow_ = matchcol_ = 0;
  exactmatch_ = 1;
  canvas_ = nil;

  for (int r = 0;  r < NUMROWS;  r++)
    for (int c = 0;  c < NUMCOLUMNS;  c++)
    {
      const int* icolor = colortable_ [r][c];
      color_ [r][c] = new Color (
        (float) icolor [0] / 0xffff,
        (float) icolor [1] / 0xffff,
        (float) icolor [2] / 0xffff,
        1.0
      );
/*
      color_ [r][c] = new Color ( // test
        (ColorIntensity) (r * 0.052),
        (ColorIntensity) (c * 0.125),
        (ColorIntensity) (1.0 - r * 0.052)
      );
*/
      Resource::ref (color_ [r][c]);
    }

  thinbrush_ = new Brush (0);  // 0 is defined to be one pixel
  Resource::ref (thinbrush_);
}


ColorSelector::~ColorSelector ()
{
  if (oc_)
    oc_->detach (this);

  // free colours
  for (int r = 0;  r < NUMROWS;  r++)
    for (int c = 0;  c < NUMCOLUMNS;  c++)
      Resource::unref (color_ [r][c]);

  Resource::unref (thinbrush_);
}


void ColorSelector::update (Observable*)
{
  const float true_R = oc_->getval (OColor::R);
  const float true_G = oc_->getval (OColor::G);
  const float true_B = oc_->getval (OColor::B);

  Display* dis = Session::instance ()->default_display ();

  float mindist = colorDistance (dis, true_R, true_G, true_B, color_ [matchrow_][matchcol_]);

  if (mindist < colorEpsilon)  // same colour, nothing to do
  {
    if (!exactmatch_)
    {
      exactmatch_ = 1;
      redraw ();
    }
    return;
  }

  int oldrow = matchrow_;
  int oldcol = matchcol_;

  // find best matching colour of table
  for (int r = 0;  r < NUMROWS;  r++)
    for (int c = 0;  c < NUMCOLUMNS;  c++)
    {
      float distance = colorDistance (dis, true_R, true_G, true_B, color_ [r][c]);

      if (distance < mindist)
      { matchrow_ = r;
        matchcol_ = c;
        mindist = distance;
      }
    } // for all colours

  int newexmatch = (mindist < colorEpsilon);
  if (newexmatch != exactmatch_)
  {
    exactmatch_ = newexmatch;
    redraw ();  // redraw's in sequence are accumulated
  }

  if (matchrow_ != oldrow || matchcol_ != oldcol)
  {
    ColorIntensity r, g, b;
    color_ [matchrow_][matchcol_]->intensities (dis, r, g, b);

    redraw ();  // update highlight
  }

} // update


void ColorSelector::redraw () const
{
  // InputHandler::redraw () does not work in cases where the
  // colour is changed via scrollbars, so have to use the stored
  // canvas and allocation (see Patch::redraw ())
  Canvas* c = canvas_;
  if (c)
    c->damage (extension_);
//else
//cerr << "warning: redraw, but canvas not defined!" << endl;
}


void ColorSelector::allocate (Canvas* c, const Allocation& a, Extension& ext)
{
  ext.set (c, a);  // use whole allocation to draw
  InputHandler::allocate (c, a, ext);   

  canvas_ = c;
  extension_ = ext;
}


void ColorSelector::undraw ()
{
  InputHandler::undraw ();
  canvas_ = nil;
}


void ColorSelector::selectCell (int newrow, int newcol, int forceupdate)
{
  // range check
  if (newrow < 0)
    newrow = 0;
  else if (newrow >= NUMROWS)
    newrow = NUMROWS - 1;

  if (newcol < 0)
    newcol = 0;
  else if (newcol >= NUMCOLUMNS)
    newcol = NUMCOLUMNS - 1;

  ColorIntensity r, g, b;
  Display* dis = Session::instance ()->default_display ();
  color_ [newrow][newcol]->intensities (dis, r, g, b);
  //cerr << "colour of selected cell: " << r << ", " << g << ", " << b << endl;

  if (newrow != matchrow_ || newcol != matchcol_)
  {
    matchrow_ = newrow;
    matchcol_ = newcol;

    // update should not waste time to find the cell clicked

    redraw ();  // update highlight

    oc_->setrgb (r, g, b);  // also calls notify to update Observers
  }
  else if (forceupdate)  // set new intensities also when selected cell did not change
  {
    oc_->setrgb (r, g, b);
  }

} // selectCell


void ColorSelector::press (const Event& e)
{
  const Allocation& a = allocation ();

//cerr << "press at (" << e.pointer_x () << ", " << e.pointer_y () << ")" << endl;

  float bottom = a.bottom ();
  float left = a.left ();
  float width = a.right () - left;
  float height = a.top () - bottom;

  // hit cell
  int newcol = (int) ((e.pointer_x () - left) * NUMCOLUMNS / width);
  int newrow = NUMROWS - (int) ((e.pointer_y () - bottom) * NUMROWS / height) - 1;

  selectCell (newrow, newcol, 1);
  // new intensities are set also when the selected color cell does not change,
  // to adjust the colour values to the selected one

} // press


void ColorSelector::keystroke (const Event& e)
{
  unsigned long keysym = e.keysym ();

//cerr << "ColorSelector got key " << keysym << endl;

  char key = '\0';
  e.mapkey (&key, 1);

  if (key == '\t')  // pass focus
  {
    if (e.shift_is_down ())
      prev_focus ();
    else
      next_focus ();
    return;
  }

  int newrow = matchrow_;
  int newcol = matchcol_;

  switch (keysym)  // cursor keys
  {
    // change column
    case XK_Left:
      newcol--;
    break;
    case XK_Right:
      newcol++;
    break;
    case XK_Home:
      newcol = 0;
    break;
    case XK_End:
      newcol = NUMCOLUMNS - 1;
    break;

    // change row
    case XK_Up:
      newrow--;
    break;
    case XK_Down:
      newrow++;
    break;
    case XK_Prior:
      newrow = 0;
    break;
    case XK_Next:
      newrow = NUMROWS - 1;
    break;
  } // switch keysym

  selectCell (newrow, newcol, 0);
  // intensities only have to be adjusted when selected cell changes

} // handleKeystroke


void ColorSelector::draw (Canvas* canv, const Allocation& a) const
{
  float bottom = a.bottom ();
  float left = a.left ();

  // sorry, InputHandler does not give me the extension
  if (!canv->damaged (extension_) || !color_ [0][0])
    return;  // no damage or unexpected error

  float width = a.right () - left;
  float height = a.top () - bottom;

//cerr << "ColorSelector::draw" << endl;
// cerr << "allocation got to draw: (" << a.left () << ", " << a.bottom () << ", " 
// << a.right () << ", " << a.top () << ") [" << width << " x " << height << "]" << endl;

  float w = width / NUMCOLUMNS;
  float h = height / NUMROWS;

  Display* dis = Session::instance ()->default_display ();
  float onepixel = dis->to_coord (1);

  for (int row = 0;  row < NUMROWS;  row++)
  {
    // color map defined from top to bottom
    float upper = bottom + (NUMROWS - row) * h;
    float lower = upper - h;

    for (int col = 0;  col < NUMCOLUMNS;  col++)
      canv->fill_rect (left + col * w, lower, left + (col + 1) * w, upper, color_ [row][col]);

  } // for each row

  // highlight matching colour
//   white_ = color_ [1][7];
//   lightgrey_ = color_ [1][2];  // AA
//   darkgrey_ = color_ [0][7];   // 77

//   left += matchcol_ * w;  // left cell border
//   bottom += (NUMROWS - matchrow_) * h;  // upper cell border

//  canv->rect (left + 1, bottom - h + 1, left + w - 1, bottom - 1, hilitcol_, hilitbrush_);

//   // outer rect
//   canv->rect (left, bottom - h + onepixel, left + w - onepixel, bottom,
//     exactmatch_ ? white_ : lightgrey_, thinbrush_);
//   // inner rect
//   canv->rect (left + onepixel, bottom - h + 2 * onepixel, left + w - 2 * onepixel, bottom - onepixel,
//     exactmatch_ ? lightgrey_ : darkgrey_, thinbrush_);

//   // dashed thick rectangle
//   canv->rect (left + onepixel, bottom - h + 2 * onepixel, left + w - 2 * onepixel, bottom - onepixel,
//     exactmatch_ ? white_ : lightgrey_, thickbrush_);
//   canv->rect (left + onepixel, bottom - h + 2 * onepixel, left + w - 2 * onepixel, bottom - onepixel,
//     exactmatch_ ? lightgrey_ : darkgrey_, dashedbrush_);

//   const Color* lttop = exactmatch_ ? white_ : lightgrey_;
//   const Color* rtbot = exactmatch_ ? lightgrey_ : darkgrey_;

  float l = left + matchcol_ * w;
  float r = l + w - onepixel;
  float t = bottom + (NUMROWS - matchrow_) * h;
  float b = t - h + onepixel;

  const Color** grey = (const Color**) &(color_ [0][0]);  // I know this is right, cxx does not
  int em = exactmatch_;

  // outer rect
  canv->line (l, b, l, t, grey [em ? 0xe : 0xa], thinbrush_);  // left
  canv->line (l, t, r, t, grey [em ? 0xe : 0xa], thinbrush_);  // top
  canv->line (l, b, r, b, grey [em ? 0x5 : 0x1], thinbrush_);  // bottom
  canv->line (r, b, r, t, grey [em ? 0x5 : 0x1], thinbrush_);  // right

  // inner rect
  l += onepixel;
  b += onepixel;
  r -= onepixel;
  t -= onepixel;
  canv->line (l, b, l, t, grey [em ? 0xd : 0x9], thinbrush_);  // left
  canv->line (l, t, r, t, grey [em ? 0xd : 0x9], thinbrush_);  // top
  canv->line (l, b, r, b, grey [em ? 0x7 : 0x5], thinbrush_);  // bottom
  canv->line (r, b, r, t, grey [em ? 0x7 : 0x5], thinbrush_);  // right

} // draw


void ColorSelector::disconnect (Observable*)
{
  oc_ = nil;
}
