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


//<file>
//
// Name:       ocolor.C
//
// Purpose:    Implementation of class OColor
//
// Created:     6 Apr 93    Michael Pichler
//
// Modified:   18 May 94    Michael Pichler
//
//
//</file>



#include "ocolor.h"
#include "adjvalue.h"
#include "hlsutil.h"

#include "field.h"

#include <IV-look/kit.h>

// sprintf
#include <stdio.h>
// atoi
#include <stdlib.h>
#include <ctype.h>
#include <iostream.h>


// static members

const float OColor::cfactor_ [OColor::_num] =
{
  255, 255, 255, 360, 100, 100
};  // conversion factors



/* conversion RGB->HLS and HLS->RGB, all (also h) in range 0..1 */

inline void RGB2HLS (float r, float g, float b, float& h, float& l, float& s)
{
  RGBtoHLS (r, g, b, h, l, s);
  h /= 360.0;
}
// note: h is undefined for grey tones, initialise it before call!


inline void HLS2RGB (float h, float l, float s, float& r, float& g, float& b)
{
  h *= 360.0;
  HLStoRGB (h, l, s, r, g, b);
}


/* OCAdjustable: adjustable colour component */
// scroll_to must tell the change the OColor


class OCAdjustable: public AdjValue
{
  public:
    OCAdjustable (float val, OColor*, int index);

    virtual void scroll_to (DimensionName, Coord);

  private:
    OColor* oc_;
    int index_;
};


OCAdjustable::OCAdjustable (float val, OColor* oc, int index)
: AdjValue (
    0.0, 1.0, val,  // all components in range 0.0 to 1.0
    OColor::itofRGBHLS (1, index),  // scroll by (logical) 1
    (float) 1 / 8   // 8 steps for paging
  )
{
  oc_ = oc;
  index_ = index;
}


void OCAdjustable::scroll_to (DimensionName, Coord value)
{
  // constrain (d, value); would need local copy of Coord!
  if (value < lower_)
    value = lower_;
  else if (value > upper_)
    value = upper_;

  if (value != curvalue_)
  {
    curvalue_ = value;
    notify_all ();
    oc_->changed (index_);
  }
} // scroll_to



/* OCFieldEditor: field editor for RGB/HLS fields */

class OCFieldEditor: public FieldEditor31
{
  public:
    OCFieldEditor (WidgetKit*, const char*, OColor*, int index);

    void accept (FieldEditor31*, char);
    void cancel (FieldEditor31*, char);

  private:
    OColor* oc_;
    int index_;
};


declareFieldEditorCallback31(OCFieldEditor)
implementFieldEditorCallback31(OCFieldEditor)


OCFieldEditor::OCFieldEditor (WidgetKit* kit, const char* valstr, OColor* oc, int index)
: FieldEditor31
  (kit, kit->style (), valstr,
   new FieldEditorCallback31(OCFieldEditor) (this, &OCFieldEditor::accept, &OCFieldEditor::cancel, nil)
  )
{
  oc_ = oc;
  index_ = index;
}


void OCFieldEditor::accept (FieldEditor31* fe, char)
{
//cerr << "OCFieldEditor::accept" << endl;

  if (index_ == OColor::HEXRGB)
  {
    const char* s = fe->string ();
    while (isspace (*s) || *s == '#')
      s++;

    float r = OColor::itofRGBHLS (OColor::hextoi (*s++) * 16 + OColor::hextoi (*s++), OColor::R);
    float g = OColor::itofRGBHLS (OColor::hextoi (*s++) * 16 + OColor::hextoi (*s++), OColor::G);
    float b = OColor::itofRGBHLS (OColor::hextoi (*s++) * 16 + OColor::hextoi (*s++), OColor::B);

//cerr << "new RGB values: " << r << ", " << g << ", " << b << endl;

    oc_->setrgb (r, g, b);  // calls notify to update observers
  }
  else
  {
    float newval = oc_->itofRGBHLS (atoi (fe->string ()), index_);

//cerr << "new value of field " << index_ << ": " << newval << endl;

    oc_->getadj (index_)->scroll_to (0, newval);

    oc_->changed (index_);  // calls notify to update observers
  }
}


void OCFieldEditor::cancel (FieldEditor31*, char)
{
//cerr << "OCFieldEditor::cancel" << endl;

  // restore value stored by scroll bars
  oc_->changed (0);  // index does not matter (also, HEXRGB is not allowed)
}



/* Implementation of class OColor */


OColor::OColor (float r, float g, float b)
{
  float value [_num];

  value [R] = r;
  value [G] = g;
  value [B] = b;
  value [H] = 0;  // for grey tones
  RGB2HLS (r, g, b, value [H], value [L], value [S]);

  WidgetKit* kit = WidgetKit::instance ();
  // calling function responsible for setting style

  for (int i = 0;  i < _num;  i++)
  {
    adj_ [i] = new OCAdjustable (value [i], this, i);
    fed_ [i] = new OCFieldEditor (kit, itoa3 (ftoiRGBHLS (value [i], i)), this, i);

    // FieldEditor31 (kit, kit->style (), coltoa (value [i], i));  // later FieldEditor

    Resource::ref (fed_ [i]);
  }
//  fed_ [HEXRGB] = new FieldBrowser (kit, kit->style (), rgbtohex (r, g, b));  // later FieldEditor
  fed_ [HEXRGB] = new OCFieldEditor (kit, rgbtohex (r, g, b), this, HEXRGB);

  Resource::ref (fed_ [HEXRGB]);

} // constructor


OColor::~OColor ()
{
  // delete Adjustables (no Resources) and unref field editors
  for (int i = 0;  i < _num;  i++)
  {
    delete adj_ [i];
    Resource::unref (fed_ [i]);
  }
  Resource::unref (fed_ [HEXRGB]);
}


const char* OColor::itoa3 (int ival)
{
  // convert integer to decimal string "123"
  static char istr [10];

  sprintf (istr, "%3d", ival);
  return istr;
  // string must be copied by caller
}


const char* OColor::rgbtohex (float r, float g, float b)
{
  // convert RGB float to hex string "aabbcc"
  static char hexval [10];
  static const char hexdigit [17] = "0123456789abcdef";

  char* h = hexval;
//  *h++ = '#';
  int i = (int) (r * 255.9);
  *h++ = hexdigit [i >> 4];
  *h++ = hexdigit [i & 15];
  i = (int) (g * 255.9);
  *h++ = hexdigit [i >> 4];
  *h++ = hexdigit [i & 15];
  i = (int) (b * 255.9);
  *h++ = hexdigit [i >> 4];
  *h++ = hexdigit [i & 15];
  *h = '\0';
//cerr << "RGB " << r << ", " << g << ", " << b << " is hex " << hexval << endl;

  return hexval;
  // string must be copied by caller
}


int OColor::hextoi (char hexdigit)
{
  // convert hex digit [0-9a-fA-F] to integer value

  if (hexdigit >= '0' && hexdigit <= '9')
    return (hexdigit - '0');
  else if (hexdigit >= 'a' && hexdigit <= 'f')
    return (hexdigit - 'a' + 10);
  else if (hexdigit >= 'A' && hexdigit <= 'F')
    return (hexdigit - 'A' + 10);
  else
    return 0;
}


Adjustable* OColor::getadj (int n)
{ return adj_ [n];
}


FieldBrowser* OColor::getfed (int n)
{ return fed_ [n];
}


float OColor::getval (int n)
{ return adj_ [n]->cur_lower (0);
}


void OColor::setrgb (float r, float g, float b)
{
  float value [_num];

  value [R] = r;
  value [G] = g;
  value [B] = b;
  value [H] = adj_ [H]->cur_lower (0);  // for grey tones
  RGB2HLS (r, g, b, value [H], value [L], value [S]);

  for (int i = 0;  i < _num;  i++)
  {
    adj_ [i]->AdjValue::scroll_to (0, value [i]);
    fed_ [i]->field (itoa3 (ftoiRGBHLS (value [i], i)));
  }
  fed_ [HEXRGB]->field (rgbtohex (r, g, b));

  notify ();  // update Obersvers

} // setrgb


void OColor::changed (int n)
{
  float r, g, b, h, l, s;

  // update other color components, avoid successive calls of this function

  switch (n)
  {
    case R: case G: case B:
      r = adj_ [R]->cur_lower (0);
      g = adj_ [G]->cur_lower (0);
      b = adj_ [B]->cur_lower (0);
      h = adj_ [H]->cur_lower (0);  // h is undefined for grey!
      RGB2HLS (r, g, b, h, l, s);
      adj_ [H]->AdjValue::scroll_to (0, h);
      adj_ [L]->AdjValue::scroll_to (0, l);
      adj_ [S]->AdjValue::scroll_to (0, s);
    break;

    case H: case L: case S:
      h = adj_ [H]->cur_lower (0);
      l = adj_ [L]->cur_lower (0);
      s = adj_ [S]->cur_lower (0);
      HLS2RGB (h, l, s, r, g, b);
      adj_ [R]->AdjValue::scroll_to (0, r);
      adj_ [G]->AdjValue::scroll_to (0, g);
      adj_ [B]->AdjValue::scroll_to (0, b);
    break;
  }

  fed_ [R]->field (itoa3 (ftoiRGBHLS (r, R)));  // former coltoa (r, R)
  fed_ [G]->field (itoa3 (ftoiRGBHLS (g, G)));
  fed_ [B]->field (itoa3 (ftoiRGBHLS (b, B)));
  fed_ [H]->field (itoa3 (ftoiRGBHLS (h, H)));
  fed_ [L]->field (itoa3 (ftoiRGBHLS (l, L)));
  fed_ [S]->field (itoa3 (ftoiRGBHLS (s, S)));
  fed_ [HEXRGB]->field (rgbtohex (r, g, b));

  notify ();  // update Observers

} // changed
