/* units.cc */

#include "units.h"
#include <ctype.h>
#include <stdio.h>
#include <math.h>
#include <strings.h>

unit::unit( cstring Name = "", const double coeff = 1, 
	    const double off = 0, u_arr array = nil ) {
  name = Name;
  coefficient = coeff;
  offset = off;
  if( array ) {
    for( int i = 0 ; i < num_basic_units ; i++ ) {
      c[i] = array[i];
    }
  } else {
    for( int i = 0 ; i < num_basic_units ; i++ ) {
      c[i] = 0;
    }
  }
}

unit_block::unit_block( void ) {
  int array[num_basic_units];
  for( int i = 0 ; i < num_basic_units ; i++ ) array[i] = 0;
  
  define( "", 1, 0, array ); /* define scalar */
  for( int i = 0 ; i < num_basic_units ; i++ ) {
    array[i] = 1;
    define( unit_name[i], unit_coeff[i], 0, array );
    array[i] = 0;
  }

  array[ num_basic_units - 1 ] = 0;
  array[ uTemp ] = 1;
  define( "degc", 1, -273.15, array );
  define( "degf", 1 / 1.8, ((-273.15*1.8)+32), array );
}

unit_block::~unit_block( void ) {
  dic_item di;
  forall_items( di, unit_dict ) {
    delete( unit_dict.inf( di ) );
  }
  unit_dict.clear();
}

unit *unit_block::define( cstring name, const double coeff, 
			  const double off, u_arr array ) {
  if( unit_dict.lookup( name ) ) return nil;

  unit *u = new unit( name, coeff, off, array );
  unit_dict.insert( name, u );
  return u;
}

unit *unit_block::lookup( cstring unit_name, double &coeff ) {
  dic_item di = unit_dict.lookup( unit_name );
  if( di == nil ) {
    int slen = unit_name.length();
    for( int i = 0 ; i < num_prefixes ; i++ ) {
      int n = strlen( prefix[i].pfix );
      if( slen > n && unit_name( 0, n - 1 ) == prefix[i].pfix ) {
	dic_item di = unit_dict.lookup( unit_name( n, slen - 1 ) );
	if( di ) {
	  coeff = prefix[i].coeff;
	  return unit_dict.inf( di );
	}
      }
    }
    for( int i = 0 ; i < num_prefixes ; i++ ) {
      if( unit_name[0] == prefix[i].abbrev ) {
	dic_item di = unit_dict.lookup( unit_name( 1, slen - 1 ) );
	if( di ) {
	  coeff = prefix[i].coeff;
	  return unit_dict.inf( di );
	}
      }
    }
    return nil;
  }
  coeff = 1;
  return unit_dict.inf( di );
}

/* this version used for scalars */
bool unit_block::parse_and_convert( cstring unit_name,  double &d, unit_vec &uv,
				    double &Coeff, const int pmod ) {
  int len = unit_name.length();
  int nm_end;

  int power;
  for( nm_end = len - 1; nm_end && isdigit( unit_name[nm_end] ) ; nm_end-- );
  if( nm_end < 0 ) return nil;
  if( nm_end < len - 1 ) {
    sscanf( unit_name(nm_end+1, len-1), "%d", &power );
    power *= pmod;
  } else power = pmod;

  string name_sec = unit_name( 0, nm_end );

  unit *U = nil;
  double coeff = 1;
  dic_item di = unit_dict.lookup( name_sec );
  if( di == nil ) {
    for( int i = 0 ; i < num_prefixes ; i++ ) {
      int n = strlen( prefix[i].pfix );
      if( nm_end >= n && name_sec( 0, n - 1 ) == prefix[i].pfix ) {
	di = unit_dict.lookup( name_sec( n, nm_end ) );
	if( di ) {
	  U = unit_dict.inf( di );
	  coeff = prefix[i].coeff;
	  break;
	}
      }
    }
    if( !U ) {
      for( int i = 0 ; i < num_prefixes ; i++ ) {
	if( name_sec[0] == prefix[i].abbrev ) {
	  dic_item di = unit_dict.lookup( name_sec( 1, nm_end ) );
	  if( di ) {
	    U = unit_dict.inf( di );
	    coeff = prefix[i].coeff;
	    break;
	  }
	}
      }
    }
  } else {
    U = unit_dict.inf( di );
  }

  if( !U ) return true;
  double offset = U->Offset();
  if( offset ) {
    if( power > 0 ) {
      for( int i = 0 ; i < power ; i++ ) {
	d = d - ( offset / Coeff )  /* * coeff * U->Coeff() */;
	Coeff = coeff * U->Coeff();
      }
    } else { /* ignore offset */
      Coeff *= pow( coeff * U->Coeff(), power );
    }
  } else {
    Coeff *= pow( coeff * U->Coeff(), power );
  }
  for( int j = 0 ; j < num_basic_units ; j++ ) {
    uv[j] += ( *U )[j] * power;
  }

  return false;
}

/* this version used for transforming matrices and vertices */
bool unit_block::parse_and_convert( cstring unit_name, double *arr,
				    const int arr_size, unit_vec &uv,
				    double &Coeff, const int pmod ) {
  int len = unit_name.length();
  int nm_end;

  int power;
  for( nm_end = len - 1; nm_end && isdigit( unit_name[nm_end] ) ; nm_end-- );
  if( nm_end < 0 ) return nil;
  if( nm_end < len - 1 ) {
    sscanf( unit_name(nm_end+1, len-1), "%d", &power );
    power *= pmod;
  } else power = pmod;

  string name_sec = unit_name( 0, nm_end );

  unit *U = nil;
  double coeff = 1;
  dic_item di = unit_dict.lookup( name_sec );
  if( di == nil ) {
    for( int i = 0 ; i < num_prefixes ; i++ ) {
      int n = strlen( prefix[i].pfix );
      if( nm_end >= n && name_sec( 0, n - 1 ) == prefix[i].pfix ) {
	di = unit_dict.lookup( name_sec( n, nm_end ) );
	if( di ) {
	  U = unit_dict.inf( di );
	  coeff = prefix[i].coeff;
	  break;
	}
      }
    }
    if( !U ) {
      for( int i = 0 ; i < num_prefixes ; i++ ) {
	if( name_sec[0] == prefix[i].abbrev ) {
	  dic_item di = unit_dict.lookup( name_sec( 1, nm_end ) );
	  if( di ) {
	    U = unit_dict.inf( di );
	    coeff = prefix[i].coeff;
	    break;
	  }
	}
      }
    }
  } else {
    U = unit_dict.inf( di );
  }
  if( !U ) return true;
  double offset = U->Offset();
  if( offset ) {
    if( power > 0 ) {
      for( int i = 0 ; i < power ; i++ ) {
	for( int n = 0 ; n < arr_size ; n++ ) {
	  arr[n] = ( arr[n] - offset / Coeff ) /** coeff * U->Coeff() */;
	}
      }
      Coeff *= coeff * U->Coeff();
    } else { /* ignore offset */
      // for( int n = 0 ; n < att_size; n++ ) {
      Coeff *= pow( coeff * U->Coeff(), power );
    }
  } else {
    //for( int n = 0 ; n < arr_size ; n++ ) {
    //arr[n] *= pow( coeff * U->Coeff(), power );
    //}
    Coeff *= pow( coeff * U->Coeff(), power );
  }
  for( int j = 0 ; j < num_basic_units ; j++ ) {
    uv[j] += ( *U )[j] * power;
  }
  return false;
}

bool unit_block::preparse_units( cstring unit_name ) {
  int len = unit_name.length();
  int nm_end;

  for( nm_end = len - 1; nm_end && isdigit( unit_name[nm_end] ) ; nm_end-- );
  if( nm_end < 0 ) return nil;
  
  string name_sec = unit_name( 0, nm_end );

  dic_item di = unit_dict.lookup( name_sec );
  if( di == nil ) {
    for( int i = 0 ; i < num_prefixes ; i++ ) {
      int n = strlen( prefix[i].pfix );
      if( nm_end >= n && name_sec( 0, n - 1 ) == prefix[i].pfix ) {
	di = unit_dict.lookup( name_sec( n, nm_end ) );
	if( di ) break;
      }
    }
    if( !di ) {
      for( int i = 0 ; i < num_prefixes ; i++ ) {
	if( name_sec[0] == prefix[i].abbrev ) {
	  dic_item di = unit_dict.lookup( name_sec( 1, nm_end ) );
	  if( di ) break;
	}
      }
    }
  } 
  return di == nil;
}

ostream &operator<<( ostream &stream, const unit_block &ub ) {
  dic_item di;
  forall_items( di, ub.unit_dict )
    stream << *( ub.unit_dict.inf( di ) ) << "\n";
  return stream;
}

double convert( const double d, const unit &from, const unit &to ) {
  return ( from.coefficient * d + from.offset ) / to.coefficient - to.offset;
}

bool unit::operator==( const unit &U ) {
  for( int i = 0 ; i < num_basic_units ; i++ ) {
    if( U.c[i] != c[i] ) return false;
  }
  return true;
}

partial_unit::partial_unit( cstring Name = "", const double coeff = 1, 
			    const double off = 0, u_arr array = nil ) {
  name = Name;
  coefficient = coeff;
  offset = off;
  if( array ) {
    for( int i = 0 ; i < num_basic_units ; i++ ) {
      c[i] = array[i];
    }
  } else {
    for( int i = 0 ; i < num_basic_units ; i++ ) {
      c[i] = 0;
    }
  }
}

void partial_unit::add_pair( cstring Name, const int power ) {
  unresolved.append( new unit_power_pair( Name, power ) );
}

unit_block ub;

/* UNIT_VEC FUNCS */
unit_vec::unit_vec( void ) {
  for( int i = 0 ; i < num_basic_units ; arr[i++] = 0 );
}
 
bool unit_vec::operator==( const unit_vec &V ) const {
  for( int i = 0 ; i < num_basic_units ; ) 
    if( arr[ i ] != V.arr[i++] ) return false;
  return true;
}

bool unit_vec::operator!=( const unit_vec &V ) const {
  for( int i = 0 ; i < num_basic_units ; ) 
    if( arr[ i ] != V.arr[i++] ) return true;
  return false;
}

bool unit_vec::operator!( void ) const {
  for( int i = 0 ; i < num_basic_units ; ) 
    if( arr[ i++ ] ) return false;
  return true;
}

void unit_vec::clear( void ) {
  bzero( arr, sizeof( u_arr ) );
}

unit_vec &unit_vec::operator=( const unit_vec &V ) {
  for( int i = 0 ; i < num_basic_units ; arr[ i ] = V.arr[i++] );
  return *this;
}
 
unit_vec &unit_vec::operator*=( const unit_vec &V ) {
  for( int i = 0 ; i < num_basic_units ; arr[ i ] += V.arr[i++] );
  return *this;
}

unit_vec &unit_vec::operator/=( const unit_vec &V ) {
  for( int i = 0 ; i < num_basic_units ; arr[ i ] -= V.arr[i++] );
  return *this;
}

unit_vec &unit_vec::operator^=( double p ) {
  for( int i = 0 ; i < num_basic_units ; i++ ) {
    arr[i] = (int)rint( arr[ i ] * p );
  }
  return *this;
}

ostream &operator<<( ostream &stream, const unit_vec &V ) {
  bool first = true;
  for( int i = 0 ; i < num_basic_units ; i++ ) {
    if( V.arr[i] > 0 ) {
      if( first ) {
	first = false;
	stream << " ";
      } else stream << "*";
      stream << unit_name[i];
    }
    if( V.arr[i] > 1 ) stream << "^" << V.arr[i];
  }
  bool notop = first;
  first = true;
  for( int i = 0 ; i < num_basic_units ; i++ ) {
    if( V.arr[i] < 0 ) {
      if( first ) { 
	first = false; 
	if( notop ) stream << " 1";
	stream << "/";
      } else stream << "*";   
      stream << unit_name[i];
    }
    if( V.arr[i] < -1 ) stream << "^" << -V.arr[i];
  }
  return stream;
}

bool unit_vec::load( cstring s, double &Coeff ) {
  Coeff = 1;
  for( int i = 0 ; i < num_basic_units ; arr[ i++ ] = 0 );

  /* get unit_def in terms of unit_power_pairs */
  int pos = 0;
  int len = s.length();
  int pow_mod = 1;

  if( s[0] == '/' ) { pow_mod = -1; pos++; }

  while( pos < len ) {
    string label = "";
    int power = 1;

    int end_pos = pos - 1;
    /* get unit string */
    while( end_pos < len - 1 && isunitchar( s[end_pos + 1] ) ) end_pos++;
    if( end_pos >= pos ) {
      string name = s( pos, end_pos );
      int npos;
      
      if( pos >= len ) break;
      /* check for ^ */
      if( s[end_pos + 1] == '^' ) {
	npos = end_pos + 2;
      } else {
	npos = end_pos + 1;
      }
      int nend = npos - 1;
      while( nend < len - 1 && isdigit( s[nend + 1] ) ) nend++;
      power = 1;
      if( nend >= npos ) {
	sscanf( s( npos, nend ), "%d", &power );
	pos = nend + 1;
      } else {
	pos = npos;
      }

      double C;
      unit *U = ub.lookup( name, C );
      if( !U ) return true;
      for( int i = 0 ; i < num_basic_units ; arr[i] += ( ( *U )[i] * power ) );
      Coeff *= U->Coeff() * C;

    } else {
      return true;
    }
    if( pos < len ) {
      /* check for '*', '-', '/' */
      switch( s[pos] ) {
      case '*' : case '-' : pos++; break;
      case '/' : pow_mod *= -1; pos++; break;
      default :
	if( !isalpha( s[pos] ) ) {
	  return true;
	}
      }
    }
  }
  return false;
}




/*#include <stdio.h>
  int main( int argc, char *argv[] ) {
  if( argc < 2 ) return 1;
  
  unit_block ub;
  int nundef = ub.load_unit_file( argv[1] );
  cerr << nundef << " undefined\n";
  
  while( 1 ) {
  cout << "> ";
  string from, to, nstr;
  double n;
  cin >> nstr;
  if( nstr == "q" || nstr == "quit" || nstr == "exit" ) break;
  if( sscanf( nstr, "%lf", &n ) ) {
  cin >> from >> to;
  
  double Fcoeff;
  double Tcoeff;
  unit *From = ub.lookup( from, Fcoeff );
  unit *To = ub.lookup( to, Tcoeff );
  if( !From || !To ) cerr << "Failed\n";
  else {
  double m = convert( n, *From, *To );
  cout << n * Fcoeff << " " << From->Name() << " = " << m * Tcoeff 
  << " " << To->Name() << "\n";
  }
  } else {
  double coeff;
  unit *U = ub.lookup( nstr, coeff );
  if( !U ) cout << "FAILED\n";
  else cout << coeff << " * " << *U << "\n";
  }
  }
  return 0;
  }
*/
