#include "types.h"
#include "genmap.h"
#include <time.h>
#include "exceptions.h"
#include <iostream.h>
#include "units.h"
#include <gennum.h>
#include <stdlib.h>
#include "tempbuf.h"

class UnitNum : public Numeric {
    DECLARE_MEMBERS(Real)
  Quantity _quant;
 public:
  virtual void printon(ostream&) const;
  UnitNum(Quantity& q) : _quant(q) { }
  UnitNum(double d, Unit& u) : _quant(d, u) { }
  int is_exact() const;
  virtual const Numeric& neg() const;
  virtual int sign() const;
  virtual const Numeric * mulDouble(double) const;
  virtual const UnitNum * unitnum() const { return this; }
  virtual const Numeric * add(const Numeric&y) const;
  virtual const Numeric * addr(const Numeric&y) const;
  virtual const Numeric * sub(const Numeric&y) const;
  virtual const Numeric * subr(const Numeric&y) const;
  virtual const Numeric * mul(const Numeric&y) const;
  virtual const Numeric * mulr(const Numeric&y) const;
  virtual const Numeric * div(const Numeric&y) const;
  virtual const Numeric * divr(const Numeric&y) const;
};

const UnitNum * Real::unitnum() const
{
  return new UnitNum(as_double(), NullUnit);
}

extern const Class * (__NumericTypeList[2]);
#define COERCE_FUNCTION DefaultCoerceTo
DEFINE_CLASS(UnitNum,__NumericTypeList)

void UnitNum::printon(ostream& outs) const
{
  _quant.print(outs);
}

int UnitNum::is_exact() const { return 0; }

int UnitNum::sign() const
{
  double d = _quant.factor();
  return (d > 0.0 ? 1 : d < 0.0 ? -1 : 0);
}

const Numeric * UnitNum::add(const Numeric& other) const
{
    const UnitNum *u_other = other.unitnum();
    if (u_other) return new UnitNum(_quant + u_other->_quant);
    else return other.addr(*this);
}
const Numeric * UnitNum::addr(const Numeric& other) const
{
    const UnitNum *u_other = other.unitnum();
    if (u_other) return new UnitNum(u_other->_quant + _quant);
    else return NULL;
}

const Numeric * UnitNum::sub(const Numeric& other) const
{
    const UnitNum *u_other = other.unitnum();
    if (u_other) return new UnitNum(_quant - u_other->_quant);
    else return other.subr(*this);
}
const Numeric * UnitNum::subr(const Numeric& other) const
{
    const UnitNum *u_other = other.unitnum();
    if (u_other) return new UnitNum(u_other->_quant - _quant);
    else return NULL;
}

const Numeric * UnitNum::mul(const Numeric& other) const
{
    const UnitNum *u_other = other.unitnum();
    if (u_other) return new UnitNum(_quant * u_other->_quant);
    else return other.mulr(*this);
}
const Numeric * UnitNum::mulr(const Numeric& other) const
{
    const UnitNum *u_other = other.unitnum();
    if (u_other) return new UnitNum(u_other->_quant * _quant);
    else return NULL;
}
const Numeric * UnitNum::div(const Numeric& other) const
{
  const UnitNum *u_other = other.unitnum();
  if (u_other)
    {
      Quantity result = _quant / u_other->_quant;
      if (&result.unit() == &NullUnit)
	return new Double(result.factor());
      return new UnitNum(result);
    }
  else return other.divr(*this);
}
const Numeric * UnitNum::divr(const Numeric& other) const
{
  const UnitNum *u_other = other.unitnum();
  if (u_other)
    {
      Quantity result = u_other->_quant / _quant;
      if (&result.unit() == &NullUnit)
	return new Double(result.factor());
      return new UnitNum(result);
    }
  else return NULL;
}

const Numeric& UnitNum::neg() const
{
  Quantity negq = - _quant;
  return *new UnitNum(negq);
}

const Numeric * UnitNum::mulDouble(double d) const
{
  Quantity resultq = _quant * d;
  return new UnitNum(resultq);
}

struct timeb;
extern "C" time_t get_date(const char*, struct timeb *);

Root* MakeDate(Root* arg)
{
  const StringC *strptr;
  char *str = Force2String(arg, NULL, &strptr);
  time_t tim = get_date(str, NULL);
  if (strptr == NULL)
    free(str);
  if (tim == (time_t)(-1))
    Signal(new GenericCondition("Bad string given to Date"));
  return new UnitNum((double)tim, DateUnit);
}

volatile void units_error()
{
  Signal(new GenericCondition("units mismatch"));
}

struct UnitEntry
{
  char *name;
  Unit *unit;
};

UnitEntry UnitTable[] = {
  { "s", &SecondsUnit },
  { "g", &GramUnit },
  { "m", &MeterUnit },
  { "h", &HoursUnit },
  { "mn", &MinutesUnit },
  { "yd", &YardUnit },
  { "ft", &FootUnit },
  { "in", &InchUnit },
  { 0, 0 }
};

Root *ParseUnitNum(istream& istr, double num)
{
  TempBuf buf;
  for (;;)
    {
      int ch = istr.get();
      if (ch == EOF)
	break;
      if (!isalpha(ch))
	{
	  istr.putback(ch);
	  break;
	}
      buf.put(ch);
    }
  buf.put(0);
  for (UnitEntry *uentry = UnitTable; uentry->name; uentry++)
    {
      if (strcmp (uentry->name, buf.string()) == 0)
	{
	  return new UnitNum(num, *uentry->unit);
	}
    }
  return NULL;
}
