#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>

#include "iupPlot.h"


const double kTickValueVeryBig = 1.0e4;     // switch to scientific format
const double kTickValueVerySmall = 1.0e-3;  // switch to scientific format
const double kLittleIncrease = 1.0001;
const double kLittleDecrease = 0.9999;
const double kEps = 1e-4;

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

static double iPlotRoundSpan(double inSpan)
{
  // round it to something producing readable tick labels
  // write it in the form inSpan = a*SafeExp10 (b)

  if (inSpan<=0)
    return (double)-1.234567;  // error

  int thePow = 0;
  double theSpan = inSpan;
  if (inSpan>1)
  {
    while (theSpan>10)
    {
      theSpan/=10;
      if (theSpan == inSpan)  // not a number
          return (double)-1.234567;
      thePow++;
    }
  }
  else
  {
    while (theSpan<1)
    {
      theSpan*=10;
      thePow--;
    }
  }

  int theRoundedFirstDigit = iupPlotRound (theSpan);
  int thePreferredFirstDigit = 1;
  switch (theRoundedFirstDigit)
  {
  case 1:
    thePreferredFirstDigit = 1;
    break;
  case 2:
  case 3:
  case 4:
    thePreferredFirstDigit = 2;
    break;
  case 5:
  case 6:
  case 7:
  case 8:
  case 9:
    thePreferredFirstDigit = 5;
    break;
  case 10:
    thePreferredFirstDigit = 1;
    thePow++;
    break;
  default:
    // error
    return (double)-1.234567;
  }
  double theRes = thePreferredFirstDigit*pow (10., thePow);
  return theRes;
}

int iupPlotCalcPrecision(double inValue)
{
  if (inValue < 0)
    inValue = -inValue;

  if (inValue > kTickValueVeryBig || inValue < kTickValueVerySmall)
    return 1;
  else
  {
    int thePrecision = 0;
    if (inValue < 1)
    {
      double theSpan = inValue;
      while (theSpan < 1 && thePrecision < 9)
      {
        thePrecision++;
        theSpan *= 10;
      }
    }

    return thePrecision;
  }
}

static void iPlotMakeAutoFormatString(double inValue, char* outFormatString, bool sign_space)
{
  if (inValue < 0)
    inValue = -inValue;

  if (inValue > kTickValueVeryBig || inValue < kTickValueVerySmall)
  {
    if (sign_space)
      iupStrCopyN(outFormatString, 30, "% .1e");
    else
      iupStrCopyN(outFormatString, 30, "%.1e");
  }
  else
  {
    int thePrecision = 0;
    if (inValue < 1)
    {
      double theSpan = inValue;
      while (theSpan < 1 && thePrecision < 9)
      {
        thePrecision++;
        theSpan *= 10;
      }
    }

    if (sign_space)
    {
      char theBuf[128] = IUP_PLOT_DEF_NUMBERFORMATSIGNED;
      theBuf[3] = (char)('0' + thePrecision); // "% ."
      iupStrCopyN(outFormatString, 30, theBuf);
    }
    else
    {
      char theBuf[128] = IUP_PLOT_DEF_NUMBERFORMAT;
      theBuf[2] = (char)('0' + thePrecision);  // "%."
      iupStrCopyN(outFormatString, 30, theBuf);
    }
  }
}

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

bool iupPlotTickIterLinear::Init()
{
  if (!mAxis)
    return false;

  double theMin = mAxis->mMin;
  double theMajorTickSpan = mAxis->mTick.mMajorSpan;
  int theDiv = mAxis->mTick.mMinorDivision;
  mDelta = theMajorTickSpan/theDiv;
  mCount = (long)ceil(theMin/mDelta);
  mCurrentTick = mCount*mDelta;

  return true;

}

bool iupPlotTickIterLinear::GetNextTick (double &outTick, bool &outIsMajorTick, char* outFormatString)
{
  (void)outFormatString; // unused here

  if (!mAxis)
    return false;

  if (mCurrentTick>mAxis->mMax*kLittleIncrease)
    return false;

  outTick = mCurrentTick;
  outIsMajorTick = (mCount%mAxis->mTick.mMinorDivision == 0);

  mCurrentTick += mDelta;
  mCount++;
  return true;
}

bool iupPlotTickIterLinear::CalculateSpacing (double inParRange, double inDivGuess, iupPlotTick &ioTick) const
{
  if (inDivGuess <= kFloatSmall)
    return false;

  double thePreferredSpan = iPlotRoundSpan(inParRange / inDivGuess);
  if (thePreferredSpan < 0)
    return false;

  double thePreferredNrOfTicks = inParRange/thePreferredSpan;
  if (thePreferredNrOfTicks <1)
    ioTick.mMajorSpan = inParRange;
  else
    ioTick.mMajorSpan = thePreferredSpan;

  ioTick.mMinorDivision = 5;

  // Calculated only once for Linear scale
  if (ioTick.mFormatAuto)
    iPlotMakeAutoFormatString(ioTick.mMajorSpan, ioTick.mFormatString, mAxis->mVertical && mAxis->mReverseTicksLabel);

  return true;
}

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

bool iupPlotTickIterLog::Init ()
{
  if (!mAxis)
    return false;

  double theMin = mAxis->mMin;
  double theMajorTickSpan = mAxis->mTick.mMajorSpan;
  int theDiv = mAxis->mTick.mMinorDivision;
  mDelta = theMajorTickSpan/theDiv;
  double theBase = mAxis->mLogBase;
  long thePowMin = (long)floor(iupPlotLog(theMin, theBase));
  mCurrentTick = iupPlotExp (thePowMin, theBase);
  mCount = 0;

  // walk to the first tick

  if (theMin<=0)
    return false;
  else
  {
    // walk forward
    double theNext = mCurrentTick+mDelta*iupPlotExp (thePowMin, theBase);
    while (theNext<=theMin*kLittleDecrease)
    {
      mCurrentTick = theNext;
      theNext += mDelta*iupPlotExp(thePowMin,theBase);
      mCount++;
    }
  }
  return true;
}

bool iupPlotTickIterLog::CalculateSpacing (double, double inDivGuess, iupPlotTick &ioTick) const
{
  if (inDivGuess<=kFloatSmall)
    return false;

  double theBase = mAxis->mLogBase;
  ioTick.mMajorSpan = theBase-1;// relative

  ioTick.mMinorDivision = iupPlotRound(ioTick.mMajorSpan);

  iupStrCopyN(ioTick.mFormatString, sizeof(ioTick.mFormatString), "%.1e");
  return true;
}

bool iupPlotTickIterLog::GetNextTick (double &outTick, bool &outIsMajorTick, char* outFormatString)
{
  if (!mAxis)
    return false;

  if (mCurrentTick>mAxis->mMax*kLittleIncrease)
    return false;

  outTick = mCurrentTick;
  outIsMajorTick = (mCount%mAxis->mTick.mMinorDivision == 0);

  // Calculated in every iteration for Log scale
  if (outFormatString && mAxis->mTick.mFormatAuto)
    iPlotMakeAutoFormatString(outTick, outFormatString, mAxis->mVertical && mAxis->mReverseTicksLabel);

  double theBase = mAxis->mLogBase;
  double theLogNow = iupPlotLog(mCurrentTick, theBase);
  int thePowNow = (int)floor(theLogNow);
  outIsMajorTick = false;
  if (fabs (theLogNow-thePowNow)<kEps)
    outIsMajorTick = true;

  mCurrentTick += mDelta*iupPlotExp(thePowNow, theBase);
  mCount++;

  return true;
}

bool iupPlotTickIterLog::AdjustRange (double &ioMin, double &ioMax) const
{
  double theBase = mAxis->mLogBase;
  if (mAxis->mMaxDecades > 0)
    ioMin = ioMax/iupPlotExp (mAxis->mMaxDecades, theBase);

  if (ioMin == 0 && ioMax == 0)
  {
    ioMin = kLogMinClipValue;
    ioMax = 1.0;
  }
  if (ioMin <= 0 || ioMax<=0)
    return false;

  ioMin = RoundDown(ioMin*kLittleIncrease);
  ioMax = RoundUp(ioMax*kLittleDecrease);

  if (ioMin<kLogMinClipValue)
    ioMin = kLogMinClipValue;

  if (mAxis->mMaxDecades > 0)
    ioMin = ioMax/iupPlotExp (mAxis->mMaxDecades, theBase);

  return true;
}

double iupPlotTickIterLog::RoundUp (double inFloat) const
{
  double theBase = mAxis->mLogBase;
  int thePow = (int)ceil(iupPlotLog(inFloat, theBase));
  return pow (theBase, thePow);
}

double iupPlotTickIterLog::RoundDown (double inFloat) const
{
  double theBase = mAxis->mLogBase;
  int thePow = (int)floor(iupPlotLog(inFloat,theBase));
  return pow (theBase, thePow);
}

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

bool iupPlotTickIterNamed::GetNextTick (double &outTick, bool &outIsMajorTick, char* outFormatString)
{
  if (iupPlotTickIterLinear::GetNextTick (outTick, outIsMajorTick, outFormatString))
  {
    int theSampleIndex = iupPlotRound(outTick);

    // TODO: improve this
    if (fabs(outTick - (double)theSampleIndex) > 0.1)
    {
      if (outFormatString) outFormatString[0] = '\0';
      return true;
    }

    if (theSampleIndex >= 0 && theSampleIndex < mStringData->GetCount())
    {
      if (outFormatString) iupStrCopyN(outFormatString, 30, mStringData->GetSampleString(theSampleIndex));
      return true;
    }
  }
  return false;
}

bool iupPlotTickIterNamed::CalculateSpacing (double inParRange, double inDivGuess, iupPlotTick &outTick) const
{
  if (iupPlotTickIterLinear::CalculateSpacing (inParRange, inDivGuess, outTick))
  {
    outTick.mMinorDivision = 1;
    return true;
  }
  return false;
}
