#include "units.h"
#include <stdlib.h>
#include <time.h>
#include <types.h>
#include <hashtab.h>
#include <stdio.h>

#ifdef __GNUC__
extern volatile void units_error();
#else
extern void units_error();
#endif

Unit* Unit::difference() { return 0; }

int BaseUnit::nextbase = 0;

char* Unit::abbreviation() const
{
  return _name+1;
}

BaseUnit::BaseUnit(char *name)
{
  _name = name;
  _index = nextbase++;
  _factor = 1.0;
}

BaseUnit::BaseUnit(char *name, Dimensions& dims) : Unit(dims)
{
  _name = name;
  _index = nextbase++;
  _factor = 1.0;
}

CompoundUnit::CompoundUnit(char *name, double f, Dimensions& dims) : Unit(dims)
{
  _name = name;
  _factor = f;
}

unsigned long Dimensions::hash(int count, BaseUnit **bases, short *exponents)
{
  unsigned long h = 0;
  for (int i = 0; i < count; i++)
    {
      h = (h << exponents[i]) + bases[i]->index();
    }
  return h;
}

int Dimensions::equal(int count, BaseUnit **b, short *e)
{
  if (size != count)
    return 0;
  for (int i = 0; i < count; i++)
    {
      if (b[i] != bases[i] || e[i] != exponents[i])
	return 0;
    }
  return 1;
}

static HashArray dim_table(16, sizeof(Dimensions*));

Dimensions** Dimensions::lookup(int count, BaseUnit **bases, short *exponents)
{
  register HashArray* tab = &dim_table;
  unsigned long hash = Dimensions::hash(count, bases, exponents);
  Dimensions *d;
#define HASH_ELEMENT_TYPE Dimensions*
#define HASH_LENGTH_LOG tab->tab_len_log
#define HASH_ELEMENT_LOG 3 /* log2(2*sizeof(void*)) */
#define HASH_EQUAL(element,arg) \
  element->equal(count, bases, exponents)
#undef HASH_NULL
#define HASH_NULL(element) (*element==HashNull)
#define HASH_DATA tab->data
#define HASH_DELETED(element)  (*element == HashDeleted)
/* the mask is redundant if masking is done before indexing */
#include "hashfunc.h"
}

Dimensions * Dimensions::find(int count, BaseUnit **bases, short *exponents)
{
  Dimensions **d = lookup (count, bases, exponents);
  if (*d)
    return *d;
  BaseUnit** b = new BaseUnit*[count];
  short* e = new short[count];
  for (int i = 0; i < count; i++)
    {
      b[i] = bases[i];
      e[i] = exponents[i];
    }
  *d = new Dimensions (count, b, e);
  return *d;
}

Unit * Dimensions::select(Unit& a, Unit& b, int op)
{
  // NOTE:  This is very simplistic.
  // There are major policy issues here.
  if (count() == 0)
    return &NullUnit;
  if (units)
    return units;
  double new_factor;
  char *new_name;
  char buf[100];
  if (op == 1) /* multiply */
    {
      sprintf (buf, "%%(%s*%s)", a.abbreviation(), b.abbreviation());
      new_factor = a.factor() * b.factor();
    }
  else /* op == -1:  divide */
    {
      sprintf (buf, "%%(%s/%s)", a.abbreviation(), b.abbreviation());
      new_factor = a.factor() / b.factor();
    }
  new_name = strdup(buf);
  return new CompoundUnit(new_name, new_factor, *this);
}

unsigned long Dimensions::hash()
{
    return hash(size, bases, exponents);
}

Quantity operator+(const Quantity& a, const Quantity& b)
{
  if (!(a.dimensions() == b.dimensions()) || (a.absolute() && b.absolute()))
    units_error();
  if (!b.absolute())
    {
      Quantity result(a.factor()
		      + b.factor() * b.unit().factor() / a.unit().factor(),
		      a.unit());
      return result;
    }
  else
    {
      Quantity result(b.factor()
		      + a.factor() * a.unit().factor() / b.unit().factor(),
		      b.unit());
      return result;
    }
}

Quantity operator-(const Quantity& a)
{
  if (a.absolute())
    units_error();
  Quantity result(- a.factor(), a.unit());
  return result;
}

Quantity operator-(const Quantity& a, const Quantity& b)
{
  if (a.dimensions() != b.dimensions())
    units_error();
  if (!a.absolute() && b.absolute())
    units_error();
  if (!b.absolute())
    {
      Quantity result(a.factor()
		      - b.factor() * b.unit().factor() / a.unit().factor(),
		      a.unit());
      return result;
    }
  else // Absolute-Absolute 
    {
      Unit *result_unit = a.unit().difference();
      Quantity result(a.factor() * a.unit().factor() / result_unit->factor()
		      - b.factor() * b.unit().factor() / result_unit->factor(),
		      *result_unit);
      return result;
    }
}

Quantity operator*(double a, const Quantity& b)
{
  if (b.absolute())
    units_error();
  Quantity result(a * b.factor(), b.unit());
  return result;
}

Quantity operator*(const Quantity& a, double b)
{
  if (a.absolute())
    units_error();
  Quantity result(a.factor() * b, a.unit());
  return result;
}

/* Combines dimensions, for multiplication (operator==1)
   or division (operator==-1) of two Quantities. */

static int CombineDimensions(BaseUnit **result_bases, short *result_exps,
			     Dimensions& A, Dimensions& B,
			     int mul_a, int mul_b)
{
  int a_i = 0, b_i = 0;
  int result_i = 0;
#define END_INDEX 999999
  while (a_i < A.count() || b_i < B.count())
    {
      int combined_exp;
      if (a_i >= A.count()) goto get_b;
      if (b_i >= B.count()) goto get_a;
      if (A.base(a_i).index() == B.base(b_i).index()) goto get_both;
      if (B.base(b_i).index() < A.base(a_i).index())
	goto get_b;
    get_a:
      result_bases[result_i] = &A.base(a_i);
      result_exps[result_i] = mul_a * A.exponent(a_i);
      result_i++; a_i++;
      continue;
    get_b:
      result_bases[result_i] = &B.base(b_i);
      result_exps[result_i] = mul_b * B.exponent(b_i);
      result_i++; b_i++;
      continue;
    get_both:
      combined_exp = mul_a * A.exponent(a_i) + mul_b * B.exponent(b_i);
      if (combined_exp != 0)
	{
	  result_exps[result_i] = combined_exp;
	  result_bases[result_i] = &A.base(a_i);
	  result_i++;
	}
      a_i++; b_i++;
      continue;
    }
  return result_i;
}

/* Return a Dimensions object for mul_a*a+mul_b*b. */

Dimensions& CombineDimensions(Dimensions& a, Dimensions& b,
			      int mul_a, int mul_b)
{
  int max_result_size = a.count()+b.count();
  BaseUnit * (result_bases[max_result_size]);
  short result_exps[max_result_size];
  int actual_result_size = CombineDimensions(result_bases, result_exps,
					     a, b, mul_a, mul_b);
  if (actual_result_size == 0)
    return NullDim;
  return *Dimensions::find(actual_result_size, result_bases, result_exps);
}

Unit& operator*(Unit&a, Unit& b)
{
  Dimensions& dim = CombineDimensions(a.dimensions(), b.dimensions(), 1, 1);
  Unit *unit = dim.select(a, b, 1);
}

Unit& operator/(Unit&a, Unit& b)
{
  Dimensions& dim = CombineDimensions(a.dimensions(), b.dimensions(), 1, -1);
  Unit *unit = dim.select(a, b, -1);
}

Quantity operator*(const Quantity& a, const Quantity& b)
{
  Unit& runit = a.unit() * b.unit();
  Quantity result(((a.factor() * a.unit().factor())
		   * (b.factor() * b.unit().factor()))
		  / runit.factor(),
		  runit);
  return result;
}

Quantity operator/(const Quantity& a, const Quantity& b)
{
  Unit& runit = a.unit() / b.unit();
  Quantity result(((a.factor() * a.unit().factor())
		   / (b.factor() * b.unit().factor()))
		  / runit.factor(),
		  runit);
//  Quantity result((a.factor() / b.factor()) / runit.factor(), runit);
  return result;
}

void BaseUnit::print(ostream& out, Quantity& quant) const
{
  out << quant.factor() << abbreviation();
}

void CompoundUnit::print(ostream& out, Quantity& quant) const
{
  out << quant.factor() << abbreviation();
}

void Quantity::print(ostream& out) const
{
  _unit->print(out, *this);
}

Dimensions NullDim(0, NULL, NULL);
static short OneExp[1] = { 1 };

static BaseUnit* (SecondsBase[1]) = { &SecondsUnit };
Dimensions TimeDim(1, SecondsBase, OneExp);
BaseUnit SecondsUnit("%s\0!seconds", TimeDim);

static BaseUnit* (MeterBase[1]) = { &MeterUnit };
Dimensions LengthDim(1, MeterBase, OneExp);
BaseUnit MeterUnit("%m\0!meter", LengthDim);

static BaseUnit* (GramBase[1]) = { &GramUnit };
Dimensions WeightDim(1, GramBase, OneExp);
BaseUnit GramUnit("%g\0!gram", WeightDim);

static BaseUnit* (KelvinBase[1]) = { &KelvinUnit };
Dimensions TemperatureDim(1, KelvinBase, OneExp);
BaseUnit KelvinUnit("%oK\0!degrees Kelvin", TemperatureDim);

CompoundUnit NullUnit("%num\0!num", 1.0, NullDim);
CompoundUnit MinutesUnit("%m\0!minutes", 60.0, TimeDim);
CompoundUnit HoursUnit("%h\0!hours", 3600.0, TimeDim);
CompoundUnit YardUnit("%yd\0!yards", 0.0254*36, LengthDim);
CompoundUnit InchUnit("%in\0!inches", 0.0254, LengthDim);
CompoundUnit FootUnit("%ft\0!feet", 0.0254*12, LengthDim);

DateUnitT DateUnit;

DateUnitT::DateUnitT()
{
  _factor = 1.0;
  _dims = &TimeDim;
}

Unit* DateUnitT::difference() { return &SecondsUnit; }

void DateUnitT::print(ostream& outs, Quantity& quant) const
{
  time_t ttim = (time_t)quant.factor();
  outs << ctime(&ttim);
}
