// @configure_input@

/**************************************************************************\
 * Copyright (c) Kongsberg Oil & Gas Technologies AS
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 * 
 * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 * 
 * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * 
 * Neither the name of the copyright holder nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\**************************************************************************/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif // HAVE_CONFIG_H

#include <cassert>
#include <cmath>
#include <cstdio>

#include <so@gui@defs.h>
#include <Inventor/@Gui@/So@Gui@Object.h> // for __COIN_SOQT__
#include <Inventor/@Gui@/widgets/SoAnyThumbWheel.h>

#ifdef __COIN_SOQT__
#include <qglobal.h> // for Q_OS_MAC
#endif

#ifndef M_PI
#define M_PI 3.14159265358979323846f
#endif // ! M_PI

/*!
  \class SoAnyThumbWheel SoAnyThumbWheel.h
  \brief The ThumbWheel class is a helper class for managing thumb wheel
  GUI widgets.
  \internal
*/

/*!
  \var int SoAnyThumbWheel::dirtyTables
  If this flag is set, the internal tables need to be recalculated.
*/

/*!
  \var int SoAnyThumbWheel::dirtyVariables
  If this flag is set, the internal variables that are calculated from the
  tables and wheel settings need to be recalculated.
*/

static unsigned int
int8clamp(float f) {
  assert(f >= 0.0f);
  if (f >= 255.0f)
    return 255;
  return (unsigned int) floor(f);
} // int8clamp()

// ************************************************************************

/*!
  Constructor.
*/

SoAnyThumbWheel::SoAnyThumbWheel(
  void)
: diameter(0)
, width(0)
, byteorder(ABGR)
, boundaryhandling(ACCUMULATE)
, movement(AUTHENTIC)
, dirtyTables(1)
, dirtyVariables(1)
{
  assert(sizeof(int) == 4 && "FIXME: use int32 datatype instead");

//  this->disabledred = 1.0f;
//  this->disabledgreen = 1.0f;
//  this->disabledblue = 1.0f;

  this->red = 220.0f / 255.0f;
  this->green = 180.0f / 255.0f;
  this->blue = 180.0f / 255.0f;
  
  this->light = 1.3f;
  this->front = 1.2f;
  this->normal = 1.0f;
  this->shade = 0.8f;

  for (int i = 0; i < NUMTABLES; i++)
    this->tables[i] = NULL;
} // SoAnyThumbWheel()

/*!
  Destructor.
*/

SoAnyThumbWheel::~SoAnyThumbWheel(
  void)
{
  for (int i = 0; i < NUMTABLES; i++)
    delete [] this->tables[i];
} // ~ThumbWheel()

// ************************************************************************

/*!
*/

void
SoAnyThumbWheel::setSize(
  const int diameterarg,
  const int widtharg)
{
  if (this->diameter != diameterarg) {
    this->dirtyTables = 1;
    this->dirtyVariables = 1;
  } else if (this->width != widtharg) {
    this->dirtyVariables = 1; // embossed squares grows...
  } else {
    return;
  }
  this->diameter = diameterarg;
  this->width = widtharg;
} // setSize()

/*!
*/

void
SoAnyThumbWheel::getSize(
  int & diameterref,
  int & widthref) const
{
  diameterref = this->diameter;
  widthref = this->width;
} // getSize()

// ************************************************************************

/*!
*/

void
SoAnyThumbWheel::setColor(
  const float redval,
  const float greenval,
  const float blueval)
{
  this->red = redval;
  this->green = greenval;
  this->blue = blueval;
} // setColor()

/*!
*/

void
SoAnyThumbWheel::getColor(
  float & redref,
  float & greenref,
  float & blueref) const
{
  redref = this->red;
  greenref = this->green;
  blueref = this->blue;
} // GetColor()

// ************************************************************************

/*!
*/

void
SoAnyThumbWheel::setColorFactors(
  const float lightval,
  const float frontval,
  const float normalval,
  const float shadeval)
{
  this->light = lightval;
  this->front = frontval;
  this->normal = normalval;
  this->shade = shadeval;
} // setColorFactors()

/*!
*/

void
SoAnyThumbWheel::getColorFactors(
  float & lightref,
  float & frontref,
  float & normalref,
  float & shaderef) const
{
  lightref = this->light;
  frontref = this->front;
  normalref = this->normal;
  shaderef = this->shade;
} // getColorFactors()

// ************************************************************************

/*!
  This method returns the number of bitmaps required to represent all the
  possible rotations of the thumb wheel.
*/

int
SoAnyThumbWheel::getNumBitmaps(
  void) const
{
  return this->width - 4 + 2 + 1;
} // getNumBitmaps()

// ************************************************************************

/*!
  This method draws thumb wheel bitmap \a number. \a direction decides
  if it should be rendered in the vertical or horizontal position.
*/

void
SoAnyThumbWheel::drawBitmap(
  const int number,
  void * bitmap,
  Alignment alignment) const
{
  if (number == 0) {
    drawDisabledWheel(number, bitmap, alignment);
  } else {
    drawEnabledWheel(number, bitmap, alignment);
  }
}

// ************************************************************************

/*!
  This method calculates the thumbwheel value based on the users mouse
  interaction.  origpos and origval is the mouse position at the moment of
  pressing the button, and origval is the thumbwheels original value at
  that point.  deltapos is the number of pixels the mouse has moved since
  pressing the button.
  
  The point in doing it this way is that the thumbwheel can easily be made
  to behave like an authentic wheel when all of these parameters are known.

  After doing a CalculateValue, you should redraw the wheel if the value has
  changed (since last time, not compared to origval).
*/

float
SoAnyThumbWheel::calculateValue(
  const float origval,
  const int origpos,
  const int deltapos) const
{
  this->validate();

//  fprintf(stderr, "calc(%8.4f, %d, %d)\n", origval, origpos, deltapos);

  float diff = 0.0f;
  switch (this->movement) {
  case UNIFORM:
    diff = this->unistep * deltapos;
    break;
  case AUTHENTIC:
    int newpos = origpos + deltapos;
    if (newpos < 0) newpos = 0;
    if (newpos >= diameter) newpos = diameter - 1;
    diff = this->tables[RAD][newpos] - this->tables[RAD][origpos];
    break;
  } // switch (this->movement)

  switch (this->boundaryhandling) {
  case MODULATE:
    while ((origval + diff) < 0.0f         ) diff += 2.0f * float(M_PI);
    while ((origval + diff) > (2.0f * float(M_PI))) diff -= 2.0f * float(M_PI);
    break;
  case CLAMP:
    if    ((origval + diff) < 0.0f         ) diff = 0.0f - origval;
    if    ((origval + diff) > (2.0f * float(M_PI))) diff = (2.0f * float(M_PI)) - origval;
    break;
  case ACCUMULATE:
    // nothing - just add the difference on top of original value
    break;
  } // switch (this->boundaryhandling)

  return origval + diff;
} // calculateValue()

// ************************************************************************

/*!
  This method returns the bitmap you need to display to represent a thumb
  wheel with the given value and active state.
*/

int
SoAnyThumbWheel::getBitmapForValue(
  const float value,
  const State state) const
{
  this->validate();

  if (state == DISABLED)
    return 0; // only one disabled bitmap in this implementation

  double squarerange = (2.0 * M_PI) / this->numsquares;
  double normalizedmodval = fmod((double) value, squarerange) / squarerange;
  if (normalizedmodval < 0.0) normalizedmodval += 1.0;
  int bitmap = 1 + (int) (normalizedmodval * (this->width - 4 + 2));
  return bitmap;
} // getBitmapForValue()

// ************************************************************************

/*!
  This method sets whether to set up uints in ARGB or BGRA mode before
  storing them in the 32-bit frame buffer.
*/

void
SoAnyThumbWheel::setGraphicsByteOrder(
  const GraphicsByteOrder byteorderval)
{
  this->byteorder = byteorderval;
} // setGraphicsByteOrder()

/*!
*/

SoAnyThumbWheel::GraphicsByteOrder
SoAnyThumbWheel::getGraphicsByteOrder(
  void) const
{
  return this->byteorder;
} // getGraphicsByteOrder()

// ************************************************************************

/*!
*/

void
SoAnyThumbWheel::setMovement(
  const Movement movementval)
{
  this->movement = movementval;
} // setMovement()

/*!
*/

SoAnyThumbWheel::Movement
SoAnyThumbWheel::getMovement(
  void) const
{
  return this->movement;
} // getMovement()

// ************************************************************************

void
SoAnyThumbWheel::setBoundaryHandling(
  const BoundaryHandling handling)
{
  this->boundaryhandling = handling;
} // setBoundaryHandling()

/*!
*/

SoAnyThumbWheel::BoundaryHandling
SoAnyThumbWheel::getBoundaryHandling(
  void) const
{
  return this->boundaryhandling;
} // getBoundaryHandling()

// ************************************************************************

/*!
  This method validates the ThumbWheel object, recalculating the tables and
  state variables if necessary.  If object is already valid, no calculations
  are performed.
*/

void
SoAnyThumbWheel::validate(// private
  void) const
{
  int i;

  if (this->dirtyTables) {
    assert(this->dirtyVariables);
    for (i = 0; i < NUMTABLES; i++) {
      if (this->tables[i]) delete [] this->tables[i];
      this->tables[i] = new float [ this->diameter ];
    }

    float radius = ((float) this->diameter + 1.0f) / 2.0f;
//    float range = 2.0f * M_PI;
    float acos0times2 = 2.0f * (float)acos(0.0);

    for (i = 0; i < this->diameter; i++) {
      if ((float) i <= radius) {
        this->tables[COS][i] = (radius - (float) i) / radius;
        this->tables[RAD][i] = (float)acos(this->tables[COS][i]);
      } else {
        this->tables[COS][i] = ((float) i - radius) / radius;
        this->tables[RAD][i] = acos0times2 - (float)acos(this->tables[COS][i]);
      }
      this->tables[SIN][i] = (float)sqrt(1.0f - this->tables[COS][i] * this->tables[COS][i]);
    }

    this->dirtyTables = 0;
  }

  if (this->dirtyVariables) {
    assert(this->dirtyTables == 0);
    if ((this->diameter % 2) == 0)
      this->unistep = this->tables[RAD][this->diameter/2] -
                      this->tables[RAD][(this->diameter/2)-1];
    else
      this->unistep = (this->tables[RAD][(this->diameter/2)+1] -
                       this->tables[RAD][(this->diameter/2)-1]) / 2.0f;

    this->squarespacing = 2.0f * this->unistep;
    this->shadelength = this->unistep; // remove?

    int squares = (int) floor(((2.0f * M_PI) /
                         ((((float) width - 4.0f) * this->unistep) + this->squarespacing)) + 0.5f);
    this->numsquares = (float)squares;
    this->squarelength = (float(2.0 * M_PI) / (float) squares) - this->squarespacing;

    this->dirtyVariables = 0;
  }
} // validate()

// ************************************************************************

inline unsigned int
SoAnyThumbWheel::swapWord(
  unsigned int orig) const
{
  // orig is RGBA
  unsigned int copy;
  if (this->byteorder == ABGR) {
    copy = (orig & 0x000000ff) <<  24;
    copy |= (orig & 0x0000ff00) << 8;
    copy |= (orig & 0x00ff0000) >> 8;
    copy |= (orig & 0xff000000) >> 24;
  } else if (this->byteorder == ARGB) {
    copy = orig >> 8;             // RGB
    // FIXME: QImage::hasAlphaBuffer() is ignored on Qt4 on Mac OS X,
    // thus we must explicitly set the alpha component to 0xff (opaque).
    // Bug reported to Trolltech. 20050805 kyrah.
#if (defined(Q_OS_MAC) && (QT_VERSION >= 0x040000))
    copy |= 0xff << 24;  // A    
#else
    copy |= (orig & 0xff) << 24;  // A    
#endif
  } else if (this->byteorder == BGRA) {
    copy = orig & 0x00ff00ff;          // G & A
    copy |= (orig & 0x0000ff00) << 16; // B
    copy |= (orig & 0xff000000) >> 16; // R
  } else {
    copy = orig; // ?
  }
  return copy;
} // swapWord()

/*!
  This method draws a wheel that has been disabled from being rotated.
*/

void
SoAnyThumbWheel::drawDisabledWheel(// private
  const int number,
  void * bitmap,
  Alignment alignment) const
{
  assert(number == 0);

  this->validate();

  unsigned int * buffer = (unsigned int *) bitmap;

  for (int j = 0; j < this->diameter; j++) {
    unsigned int lightval, frontval, normalval, shadeval;
    lightval = int8clamp(255.0f * this->tables[SIN][j] * 1.15f);
    frontval = 0;
    normalval = int8clamp(255.0f * this->tables[SIN][j]);
    shadeval = int8clamp(255.0f * this->tables[SIN][j] * 0.85f);

    // rgbx
    lightval = (lightval << 24) | (lightval << 16) | (lightval << 8);
    frontval = (frontval << 24) | (frontval << 16) | (frontval << 8);
    normalval = (normalval << 24) | (normalval << 16) | (normalval << 8);
    shadeval = (shadeval << 24) | (shadeval << 16) | (shadeval << 8);

    if (this->byteorder != RGBA) {
      lightval = this->swapWord(lightval);
      frontval = this->swapWord(frontval);
      normalval = this->swapWord(normalval);
      shadeval = this->swapWord(shadeval);
    }

    if (alignment == VERTICAL) {
      buffer[j*this->width] = lightval;
      for (int i = 1; i < (width - 1); i++)
        buffer[(j*this->width)+i] = normalval;
      buffer[(j*this->width)+(this->width-1)] = shadeval;
    } else {
      buffer[j] = lightval;
      for (int i = 1; i < (this->width-1); i++)
        buffer[j+(i*this->diameter)] = normalval;
      buffer[j+((this->width-1)*this->diameter)] = shadeval;
    }
  }
} // drawDisabledWheel()

// ************************************************************************

/*
  This method draws the thumb wheel.
  Shading/lighting aliasing can still be done better...
  Can also optimize the drawing to use less computing power...
*/

void
SoAnyThumbWheel::drawEnabledWheel(
  const int number,
  void * bitmap,
  Alignment alignment) const
{
  this->validate();

  int numEnabledBitmaps = this->width - 4 + 2;
  assert(number > 0 && number <= numEnabledBitmaps);

  float modulo = (2.0f * (float)M_PI) / this->numsquares;
  float radian = modulo - (((2.0f * (float)M_PI) / this->numsquares) * (((float) (number - 1)) / (float) numEnabledBitmaps));

  int newsquare = 1;
  int flag = 1;
  unsigned int * buffer = (unsigned int *) bitmap;
  for (int j = 0; j < this->diameter; j++) {
    unsigned int lightval, frontval, normalval, shadeval, color;

    lightval  = (int8clamp(255.0f * this->red * this->tables[SIN][j] * this->light) << 24) +
             (int8clamp(255.0f * this->green * this->tables[SIN][j] * this->light) << 16) +
             (int8clamp(255.0f * this->blue * this->tables[SIN][j] * this->light) << 8);
    frontval  = (int8clamp(255.0f * this->red * this->tables[SIN][j] * this->front) << 24) +
             (int8clamp(255.0f * this->green * this->tables[SIN][j] * this->front) << 16) +
             (int8clamp(255.0f * this->blue * this->tables[SIN][j] * this->front) << 8);
    normalval = (int8clamp(255.0f * this->red * this->tables[SIN][j] * this->normal) << 24) +
             (int8clamp(255.0f * this->green * this->tables[SIN][j] * this->normal) << 16) +
             (int8clamp(255.0f * this->blue * this->tables[SIN][j] * this->normal) << 8);
    shadeval  = (int8clamp(255.0f * this->red * this->tables[SIN][j] * this->shade) << 24) +
             (int8clamp(255.0f * this->green * this->tables[SIN][j] * this->shade) << 16) +
             (int8clamp(255.0f * this->blue * this->tables[SIN][j] * this->shade) << 8);

    if (this->byteorder != RGBA) {
      lightval = this->swapWord(lightval);
      normalval = this->swapWord(normalval);
      frontval = this->swapWord(frontval);
      shadeval = this->swapWord(shadeval);
    }

    if (newsquare) {
      color=frontval;
      newsquare = 0;
      flag = 1;
    } else {
      if (flag == 1) {
        if (j < (this->diameter * 2 / 3))
          color = shadeval;
        else
          color = normalval;
        flag = 0;
      } else {
        color = normalval;
      }
    }

    if (alignment == VERTICAL) {
      buffer[(this->width*j)] = frontval;
      buffer[(this->width*j)+1] = frontval;
      if (flag == 1) buffer[(this->width*j)+2] = frontval;
      else           buffer[(this->width*j)+2] = shadeval;

      for (int i = 3; i < (this->width-2); i++)
        buffer[(j*this->width)+i] = color;

      if (flag == 1) buffer[(this->width*j)+this->width-3] = frontval;
      else           buffer[(this->width*j)+this->width-3] = normalval;
      buffer[(this->width*j)+this->width-2] = frontval;
      buffer[(this->width*j)+this->width-1] = frontval;
    } else {

      buffer[j] = frontval;
      buffer[j+this->diameter] = frontval;
      if (flag == 1) buffer[j+(this->diameter*2)] = frontval;
      else           buffer[j+(this->diameter*2)] = shadeval;

      for (int i = 2; i < (this->width-2); i++)
        buffer[j+(i*this->diameter)] = color;

      if (flag == 1) buffer[j+(this->diameter*(this->width-3))] = frontval;
      else           buffer[j+(this->diameter*(this->width-3))] = normalval;
      buffer[j+(this->diameter*(this->width-2))] = frontval;
      buffer[j+(this->diameter*(this->width-1))] = frontval;
    }

    if (j < (this->diameter - 1)) {
      radian += this->tables[RAD][j+1] - this->tables[RAD][j];
      if (radian > modulo) {
        if (alignment == VERTICAL) {
          int color = 0;
          if (j > (this->diameter * 2 / 3))
            color = lightval;
          else if (j > (this->diameter / 3))
            color = frontval;
          if (color != 0)
            for (int i = 3; i < (this->width-2); i++)
              buffer[(j*this->width)+i] = color;
        } else {
          int color = 0;
          if (j > (this->diameter * 2 / 3))
            color = lightval;
          else if (j > (this->diameter / 3))
            color = frontval;
          if (color != 0)
            for (int i = 3; i < (this->width-2); i++)
              buffer[j+(this->diameter*i)] = color;
        }
        radian = (float) fmod(radian, modulo);
        newsquare = 1;
      }
    }
  }
} // drawEnabledWheel()

// ************************************************************************
