/***************************************************************************
                          ksthistogram.cpp: Histogram for KST
                             -------------------
    begin                : July 2002
    copyright            : (C) 2002 by C. Barth Netterfield
    email                :
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

/** A class for handling histograms for kst
 *@author C. Barth Netterfield
 */

#include "ksthistogram.h"
#include <stdlib.h>
#include <math.h>

#include <qstylesheet.h>

#include <kglobal.h>
#include <klocale.h>

#include "kstdatacollection.h"
#include "ksthsdialog_i.h"

static const QString& BINS = KGlobal::staticQString("B");
static const QString& HIST = KGlobal::staticQString("H");

KstHistogram::KstHistogram(const QString &in_tag, KstVectorPtr in_V,
                           double xmin_in, double xmax_in,
                           int in_n_bins,
                           KstHsNormType in_norm_mode)
: KstDataObject() {
  setRealTimeAutoBin(false);

  commonConstructor(in_tag, in_V, xmin_in, xmax_in, in_n_bins, in_norm_mode);
}

KstHistogram::KstHistogram(QDomElement &e)
: KstDataObject(e) {
  QString in_tag;
  KstVectorPtr in_V;
  double xmax_in=1, xmin_in=-1;
  int in_n_bins = 10;
  KstHsNormType in_norm_mode;

  setRealTimeAutoBin(false);

  in_norm_mode = KST_HS_NUMBER;
  /* parse the DOM tree */
  QDomNode n = e.firstChild();
  while( !n.isNull() ) {
    QDomElement e = n.toElement(); // try to convert the node to an element.
    if( !e.isNull() ) { // the node was really an element.
      if (e.tagName() == "tag") {
        in_tag = e.text();
      } else if (e.tagName() == "vectag") {
        KST::vectorList.lock().readLock();
        KstVectorList::Iterator it = KST::vectorList.findTag(e.text());
        if (it != KST::vectorList.end()) {
          in_V = *it;
        }
        KST::vectorList.lock().readUnlock();
      } else if (e.tagName() == "NormMode") {
        if (e.text()=="NUMBER") in_norm_mode = KST_HS_NUMBER;
        else if (e.text()=="PERCENT") in_norm_mode = KST_HS_PERCENT;
        else if (e.text()=="FRACTION") in_norm_mode = KST_HS_FRACTION;
        else if (e.text()=="MAX_ONE") in_norm_mode = KST_HS_MAX_ONE;
      } else if (e.tagName() == "minX") {
        xmin_in = e.text().toDouble();
      } else if (e.tagName() == "maxX") {
        xmax_in = e.text().toDouble();
      } else if (e.tagName() == "numBins") {
        in_n_bins = e.text().toInt();
      } else if (e.tagName() == "realtimeautobin") {
        _realTimeAutoBin = (e.text() != "0");
      }
    }
    n = n.nextSibling();
  }

  commonConstructor(in_tag, in_V, xmin_in, xmax_in, in_n_bins, in_norm_mode);

}


void KstHistogram::commonConstructor(const QString &in_tag, KstVectorPtr in_V,
                                     double xmin_in,
                                     double xmax_in,
                                     int in_n_bins,
                                     KstHsNormType in_norm_mode) {
  _typeString = i18n("Histogram");

  NormMode = in_norm_mode;

  setTagName(in_tag);
  V = in_V;

  if (xmax_in>xmin_in) {
    MaxX = xmax_in;
    MinX = xmin_in;
  } else {
    MinX = xmax_in;
    MaxX = xmin_in;
  }
  if (MaxX==MinX) {
    MaxX+=1;
    MinX-=1;
  }

  NBins = in_n_bins;
  if (NBins<2) {
    NBins = 2;
  }
  Bins = new unsigned long[in_n_bins];

  KstVectorPtr iv = new KstVector(in_tag+"-bins", NBins);
  KST::addVectorToList(iv);
  _bVector = _outputVectors.insert(BINS, iv);

  iv = new KstVector(in_tag+"-sv", NBins);
  KST::addVectorToList(iv);
  _hVector = _outputVectors.insert(HIST, iv);

  update();
}

KstHistogram::~KstHistogram() {
  _bVector = _outputVectors.end();
  _hVector = _outputVectors.end();
  KST::vectorList.lock().writeLock();
  KST::vectorList.remove(_outputVectors[BINS]);
  KST::vectorList.remove(_outputVectors[HIST]);
  KST::vectorList.lock().writeUnlock();

  delete[] Bins;
}

KstObject::UpdateType KstHistogram::update(int update_counter) {
  int i_bin, i_pt, ns;
  double y=0;
  double MaxY;

  if (KstObject::checkUpdateCounter(update_counter))
    return NO_CHANGE;

  if (update_counter > 0) {
    V->update(update_counter);
  }

  //do auto-binning if necessary
  if (_realTimeAutoBin) {
    int temp_NBins;
    double temp_xMin, temp_xMax;
    KstHistogram::AutoBin(V, &temp_NBins, &temp_xMax, &temp_xMin);
    setNBins(temp_NBins);
    setXRange(temp_xMin, temp_xMax);
  }

  NS = 3*NBins+1;
  W = (MaxX - MinX)/double(NBins);

  for (i_bin = 0; i_bin < NBins; i_bin++) {
    Bins[i_bin] = 0;
  }

  ns = V->sampleCount();
  for (i_pt = 0; i_pt < ns ; i_pt++) {
    y = V->interpolate(i_pt, ns);
    i_bin = (int)floor((y-MinX)/W);
    if ((i_bin >= 0) && (i_bin < NBins)) {
      Bins[i_bin]++;
    }
  }

  MaxY = 0;

  for (i_bin=0; i_bin<NBins; i_bin++) {
    y=Bins[i_bin];
    if (y>MaxY) MaxY = y;
  }

  switch (NormMode) {
      case KST_HS_NUMBER:
        Normalization = 1.0;
        break;
      case KST_HS_PERCENT:
        if (ns>0) Normalization = 100.0/(double)ns;
        else Normalization = 1.0;
        break;
      case KST_HS_FRACTION:
        if (ns>0) Normalization = 1.0/(double)ns;
        else Normalization = 1.0;
        break;
      case KST_HS_MAX_ONE:
        if (MaxY>0) Normalization = 1.0/MaxY;
        else Normalization = 1.0;
        break;
      default:
        Normalization = 1.0;
        break;
  }

  double *bins =  (*_bVector)->value();
  double *hist =  (*_hVector)->value();

  for ( i_bin = 0; i_bin<NBins; i_bin++ ) {
    bins[i_bin] = ( double( i_bin )+ 0.5 )*W + MinX;
    hist[i_bin] = Bins[i_bin]*Normalization;
  }

  (*_bVector)->update(update_counter);
  (*_hVector)->update(update_counter);

  return UPDATE;
}

void KstHistogram::point(int i, double &x, double &y) {
  x = (i+1)/3 * W + MinX;
  if (i%3==0) {
    y = 0;
  } else if ((i>=0) && (i<NS)) {
    y = Bins[i/3]*Normalization;
  } else {
    y=0;
  }
}

int KstHistogram::nBins() const {
  return NBins;
}

void KstHistogram::setXRange(double xmin_in, double xmax_in) {
  if (xmax_in > xmin_in) {
    MaxX = xmax_in;
    MinX = xmin_in;
  } else if (xmax_in<xmin_in) {
    MinX = xmax_in;
    MaxX = xmin_in;
  } else {
    MinX = xmax_in-1;
    MaxX = xmax_in+1;
  }
  W = (MaxX - MinX)/double(NBins);
}

void KstHistogram::setNBins(int in_n_bins) {
  delete[] Bins;

  NBins = in_n_bins;

  if (NBins<2) NBins = 2;
  Bins = new unsigned long[in_n_bins];
  W = (MaxX - MinX)/(double)NBins;

  (*_bVector)->resize(NBins);
  (*_hVector)->resize(NBins);

}

QString KstHistogram::vTag() const {
  return V->tagName();
}

void KstHistogram::setVector(KstVectorPtr new_v) {
  V = new_v;
}

QString KstHistogram::yLabel() const {
  switch (NormMode) {
    case KST_HS_NUMBER:
      return i18n("Number in Bin");
      break;
    case KST_HS_PERCENT:
      return i18n("Percent in Bin");
      break;
    case KST_HS_FRACTION:
      return i18n("Fraction in Bin");
      break;
    case KST_HS_MAX_ONE:
      return i18n("Histogram");
      break;
  }
  return i18n("Histogram");
}

QString KstHistogram::xLabel() const {
  return V->label();
}

KstCurveType KstHistogram::type() const {
  return KST_HISTOGRAM;
}

void KstHistogram::save(QTextStream &ts, const QString& indent) {
  // FIXME: clean this up - all lower case nodes, maybe save points in the
  // point class itself, etc
  QString l2 = indent + "  ";
  ts << indent << "<histogram>" << endl;
  ts << l2 << "<tag>" << QStyleSheet::escape(tagName()) << "</tag>" << endl;
  ts << l2 << "<vectag>" << QStyleSheet::escape(V->tagName()) << "</vectag>" << endl;

  ts << l2 << "<numBins>"  << NBins << "</numBins>" << endl;
  ts << l2 << "<realtimeautobin>" << _realTimeAutoBin << "</realtimeautobin>" << endl;
  ts << l2 << "<minX>" << MinX << "</minX>" << endl;
  ts << l2 << "<maxX>" << MaxX << "</maxX>" << endl;
  switch (NormMode) {
    case KST_HS_NUMBER:
      ts << l2 << "<NormMode>NUMBER</NormMode>" << endl;
      break;
    case KST_HS_PERCENT:
      ts << l2 << "<NormMode>PERCENT</NormMode>" << endl;
      break;
    case KST_HS_FRACTION:
      ts << l2 << "<NormMode>FRACTION</NormMode>" << endl;
      break;
    case KST_HS_MAX_ONE:
      ts << l2 << "<NormMode>MAX_ONE</NormMode>" << endl;
      break;
  }
  ts << indent << "</histogram>" << endl;
}

double KstHistogram::vMax() const {
  return V->max();
}

double KstHistogram::vMin() const {
  return V->min();
}

int KstHistogram::vNumSamples() const {
  return V->sampleCount();
}

QString KstHistogram::propertyString() const {
  return i18n("Histogram: %1").arg(vTag());
}

void KstHistogram::_showDialog() {
  KstHsDialogI::globalInstance()->show_Edit(tagName());
}

void KstHistogram::AutoBin(KstVectorPtr V, int *n, double *max, double *min) {
  double m;

  *max = V->max();
  *min = V->min();
  *n = V->sampleCount();

  if (*max < *min) {
    m = *max;
    *max = *min;
    *min = m;
  }
  if (*max == *min) {
    *max += 1;
    *min -= 1;
  }

  // we can do a better job auto-ranging using the tick rules from plot...
  // this has not been done yet, you will notice...
  *n /= 50;
  if (*n < 6) *n = 6;
  if (*n > 60) *n = 60;

  m = (*max - *min)/(100.0*double(*n));
  *max += m;
  *min -= m;
}

bool KstHistogram::slaveVectorsUsed() const {
  return true;
}

void KstHistogram::setRealTimeAutoBin(bool autoBin) {
  _realTimeAutoBin = autoBin;
}

bool KstHistogram::realTimeAutoBin() {
  return _realTimeAutoBin;
}

// vim: ts=2 sw=2 et

