/*
AxisScale.c
Automatic scales axes for diagrams and data plots
|_;_;_;_;_|_;_;_;_;_| this is 2 majors with 5 minors each; x1=0, x2=20
0         10        20
Version 0.03
S. Salewski 10-JUN-2010
License: GPL
gcc -lm -Wall AxisScale.c
*/

#include "AxisScale.h"

#include <limits.h>
#include <math.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>

#define MinMajors 1
#define MaxMajors 50
#define MinMinors 1
#define MaxMinors 10
#define Subdivisions 3

/* Interval size, each followed by (Subdivisions) Subintervals, terminated by 0 */
/* Iterval has to be an integer multiple of subintervals! Descending ordering! */
static int interval[3*4+1] = {100,50,20,10, 50,25,10,5, 20,10,5,2, 0};
/* we may add something like 90,30,10,10, 80,40,20,10, 40,20,10,5, 30,15,10,5, 25,5,5,5  */

static int doubletonearestint(double x)
{
  if (x > 0.0) return x + 0.5; else return x - 0.5;
}

// +7 / 3 ==> +2
// -7 / 3 ==> -3
// j > 0 always
static int intdivtoleft(int i, int j)
{
  if (i > 0) return i / j; else return (i - j + 1) / j;
}

// +7 / 3 ==> +3
// -7 / 3 ==> -2
// j > 0 always
static int intdivtoright(int i, int j)
{
  if (i > 0) return (i + j - 1) / j; else return i / j;
}

/*
x1, x2: range to partition
majors, minors: DESIRED! number of intervals==(tics-1). 1 <= majors <= 50, 1 <= minors <= 10.
extend: if true, we may extend bounds with major tics, else only with subtics.
array t holds position of tics. Its size should be at least 3*majors*minors.
For major tics we may generate a string and write it at that position in diagram, for subtics we may print a |.
return: total number of generated tics.
*/
int
scaleaxis(double x1, double x2, int majors, int minors, int arraysize, bool extend, Tic t[])
{
  double y1, y2;
  double offset, scale, val;
  int i1, i2, j1, j2, k1, k2;
  int maj, fail, bestintervalindex, iv, s, subs, i, pos;

  // for debugging only
  printf("x1=%g x2=%g majors=%d minors=%d extend=%s\n", x1, x2, majors, minors, extend==true?"true":"false");

  if ((x1 == x2) || (majors < 1)) // or NAN
    return 0;
  if (minors < 1) minors = 1; 

  if (x2 < x1) {y1 = x1; x1 = x2; x2 = y1;}
  
  // scale x1, x2 so that 1e3 <= (x2-x1) <= 1e4
  scale = floor(log10(x2 - x1));
  scale = pow(10.0, scale - 3.0);
  y1 = x1 / scale;
  y2 = x2 / scale;

  i = (y2 - y1) / interval[0]; // we will get at least i majors, scale if too many
  if (i > 10 * majors) // $$$
  {
    y1 *= 0.01;
    y2 *= 0.01;
    scale *=100.0;
  }
  else if (i > majors) // $$$
  {
    y1 *= 0.1;
    y2 *= 0.1;
    scale *=10.0;
  }

  // split value with smaller absolute value into 4 least significant decimals and offset
  offset = 0.0;
  if (y2 < 0.0) // both negative
    offset = ((int) (y2 * 1e-4)) * 1e4;
  else if (y1 > 0.0) // both positive
    offset = ((int) (y1 * 1e-4)) * 1e4;
  y1 = y1 - offset;
  y2 = y2 - offset;

  // now abs(y1) <= 20000 and abs(y2) <= 20000 -- smaler by factor 100 or 10 if code marked with $$$ above is executed!
  // we partition this range first, later we add offset again and scale back  
  i1 = doubletonearestint(y1);
  i2 = doubletonearestint(y2);

  // now search for best major partition 
  i = 0;
  fail = INT_MAX;
  while ((iv = interval[i]))
  {
    j1 = intdivtoleft(i1, iv) * iv;
    j2 = intdivtoright(i2, iv) * iv;
    maj = (j2 - j1) / iv;
    if (abs(majors - maj) < fail)
    {
      bestintervalindex = i;
      k1 = j1;
      k2 = j2;
      fail = abs(majors - maj);
    }
    i += Subdivisions + 1;
  }

  // and minor partition
  iv = interval[bestintervalindex];
  fail = INT_MAX;
  for (i = 0; i <= Subdivisions; i++)
  {
    s = iv / interval[bestintervalindex + i];
    if (abs(minors - s) < fail)
    {
      subs = s;
      fail = abs(minors - s);
    }
  }

  // generate array with tics
  i = 0;
  s = iv / subs; // step
  pos = k1; // start position
  while ((i < arraysize) && (pos <= k2))
  {
    val = (pos + offset) * scale;
    if ((extend == false) && (i > 0) && (val < x1)) // ignore previous
      i--;
    t[i].val = val;
    if ((pos % iv) == 0) t[i].major = true; else  t[i].major = false;
    pos += s;
    i++;
    if ((extend == false) && (val >= x2)) break;
  }
  return i;
}

// simple output for testing/debugging
void
printscale(Tic t[], int count)
{
  int i;
  for (i = 0; i < count; i++)
  {
    if (t[i].major == true)
      printf("|");
    else
      printf(".");
  }
  printf("\n");
  for (i = 0; i < count; i++)
  {
    if (t[i].major == true)
      printf("%g ", t[i].val);
  }
  printf("\n\n");
}

