/* units.h */

#ifndef UNITS__H
#define UNITS__H

#include "common.h"
#include <LEDA/basic.h>
#include <LEDA/dictionary.h>
#include <LEDA/list.h>
#include <iostream.h>

const char *const unit_standard = "SI";

/*    Basic Units: 		SI
    0 Mass		   	kg
    1 Length			meter
    2 Time			second
    3 Charge			Coulomb
    4 Temperature		Kelvin
    5 Light			Lumen
    6 Mole			6.03E23
   --------------------------------
    7 Angle			Radian
    8 Money			Dollar
    9 Information		Bit */

const int num_prefixes = 9;
struct prefix_s {
  const char *const pfix;
  const char abbrev;
  const double coeff;
  prefix_s( const char *const Pfix, const char Abbrev, const double Coeff ) :
    pfix(Pfix), abbrev(Abbrev), coeff(Coeff) {}
};

const prefix_s prefix[num_prefixes] = { prefix_s( "pico",  'p', 1e-12 ),
					prefix_s( "nano",  'n', 1e-9 ),
					prefix_s( "micro", 'u', 1e-6 ),
					prefix_s( "milli", 'm', 1e-3 ),
					prefix_s( "centi", 'c', 1e-2 ),
					prefix_s( "kilo",  'k', 1e3 ),
					prefix_s( "mega",  'M', 1e6 ),
					prefix_s( "giga",  'G', 1e9 ),
					prefix_s( "tera",  'T', 1e12 ) };

/* indexs into quantity_name, unit_name, unit_coeff, and unit_discrete */
enum basic_unit_index { uMass = 0, uDisp, uTime, uCharge, uTemp, uIllum, 
			uAmount, uAngle, uMoney, uInfo, uEND };

const int num_basic_units = uEND;

/* names of basic unit */
const char *const unit_name[num_basic_units] = { "kilogram", "meter", "second",
						 "coulomb", "kelvin", "candela",
						 "mole", "radian", "dollar", 
						 "bit" };

/* name of quantity corresponding to each basic unit */
const char *const quantity_name[num_basic_units] = { "Mass",
						     "Displacement",
						     "Time",
						     "Charge",
						     "Temperature",
						     "Illumination",
						     "Amount", 
						     "Angle",
						     "Money",
						     "Information" };

/* coefficient for basic units */
const double unit_coeff[num_basic_units] = { 1, 1, 1, 1, 1, 1, 
					     1/6.022169e23, 1, 1, 1 };

/* - value with a given unit_vector can only be discrete if all basic units have
   zero exponent or have positive exponents and are true in this array.
   - money should be discrete multiple of .01 dollars, but not yet this way */
const bool unit_discrete[num_basic_units] = { false, false, false, false, false,
					      false, false, false, false, true};

/* integer array of powers of basic units */
typedef int u_arr[num_basic_units];

/* used to store unit information in global list for lookup */
struct unit {
protected:
  string name;
  double coefficient;
  double offset;
  u_arr c;

public:
  unit( cstring = "", const double = 1, const double = 0, u_arr = nil );

  int operator[]( int i ) { 
    if( i < 0 || i > num_basic_units ) return 0; else return c[i]; }

  bool operator==( const unit & );

  /* assumes unit types equivalent */
  friend double convert( const double, const unit &, const unit & );
  friend ostream &operator<<( ostream &, const unit & );

  string Name() { return name; }
  double Coeff() { return coefficient; }
  double Offset() { return offset; }
  bool Discrete() { return false; }
};

/* UNIT FILE PARSING */
/* read file into list of partial units then iterate through resolving when
   possible until all resolved. break on error if no progress made */
struct unit_power_pair {
  string name;
  int power;

  unit_power_pair( cstring Name, const int Power ) { 
    name = Name; power = Power; }
};

/* partial units used in parsing of unit file */
struct partial_unit {
  string name;
  double coefficient;  /* coefficient degC, degK = 1, coefficient degF = 1.8 */

  /* offset degC = -273.15.
     offset degF = -459.67 = -( 273.15 * 1.8 + 32 ).
     offset degK = 0 */
  double offset; 
  u_arr c; /* c degC, degF, degK = { 0, 0, 0, 0, 1, 0, 0, 0, 0, 0 } */

  list<unit_power_pair *> unresolved;
  
  partial_unit( cstring = "", const double = 1, const double = 0, 
		u_arr = nil );

  ~partial_unit( void ) { 
    unit_power_pair *up; forall( up, unresolved ) delete( up ); }
  void add_pair( cstring, const int );

  friend ostream &operator<<( ostream &, const partial_unit & );
};

/* unit_vecs used in dc_udata */
class unit_vec {
private:
  u_arr arr;
public:
  int &operator[]( const int i ) { return arr[i]; }

  unit_vec( void );
  
  unit_vec &operator=( const unit_vec & );
  unit_vec &operator*=( const unit_vec & );
  unit_vec &operator/=( const unit_vec & );
  unit_vec &operator^=( double ); /* powers will be rounded to integer */
  bool operator==( const unit_vec & ) const;
  bool operator!=( const unit_vec & ) const;
  bool operator!( void ) const; /* true if all 0s */
  void clear( void );

  /* does not necessarily output the same string as was used to parse */
  friend ostream &operator<<( ostream &, const unit_vec & );

  bool load( cstring, double & );
};

/* one global unit_block exists at root that stores all unit info */
class unit_block {
private:
  dictionary<string,unit *> unit_dict;
  
public:
  unit_block( void );
  ~unit_block( void );
  
  int load_unit_file( const char * ); /* returns the number that could not be
					   parsed */
  unit *lookup( cstring, double& );
  bool parse_and_convert( cstring, double&, unit_vec&, double &, const int );
  bool parse_and_convert( cstring, double*, const int, unit_vec&, double &, 
			  const int );
  bool preparse_units( cstring );

  unit *define( cstring, double, double, u_arr );

  friend ostream &operator<<( ostream &, const unit_block & );
};

extern unit_block ub;

double convert( const double d, const unit &from, const unit &to );
bool parse_unit_def( partial_unit & );

/* only $,% and alphabet characters */
bool isunitchar( const char c );

/* UNIT FILE GRAMMAR
   unit_file  -> <ufile_item>[<newline><unit_file>]
   ufile_item -> <unit_def>
               | '//'<line_comment>
   unit_def   -> <unit_name> [<coeff>] [/]<unit_expr>
   unit_name  -> '$'
               | '%'
	       | <alphabet_character>
   unit_expr  -> <unit_name>[<power>]['-'<unit_expr>]
               | <unit_name>[<power>]'*'<unit_expr>
	       | <unit_name>[<power>]'/'<unit_expr>

              // ex. 3e-2 = 300, 1|6 = .1666..., 5.0-2 = .005
   coeff      -> <real>[('+'|'-')<int>]['|'<real>[('+'|'_')<int>]]

   UNIT GRAMMAR
   unit_expr  -> <unit_name>[<power>]['-'<unit_expr>]
               | <unit_name>[<power>]'*'<unit_expr>
	       | <unit_name>[<power>]'/'<unit_expr>
   */

#endif
