/* parse_unit.cc */

#include "units.h"
#include <fstream.h>
#include "ctype.h"
#include <stdio.h>
#include <sys/ddi.h>
#include <math.h>

static string line = "";
static int pos =  -1;
static int len = -1;
static istream *stream = &cin;
static int lnum = 1;

bool isunitchar( const char c ) {
  return isalpha( c ) || c == '$' || c == '%';
}

void flush_line( void ) { line = ""; pos = len = -1; }

void set_stream( istream &i = cin ) { stream = &i; lnum = 0; flush_line(); }

bool get_line( void ) {
  do {
    line.read_line( *stream );
    lnum++;
    if( !line.length() ) {
      return true;
    }
  } while( !line.length() );
  pos = 0;
  int comment_pos = line.pos( "//" );
  len = comment_pos == -1 ? line.length() : comment_pos;
  return len == 0;
}

int line_num( void ) { return lnum; }

bool get_string( string &s ) {
  if( pos >= len ) {
    return true;
  }

  /* eat whitespace */
  while( pos < len && isspace( line[pos] ) ) pos++;
  if( pos >= len ) return true;
  int end_pos = pos;
  while( end_pos + 1 < len && !isspace( line[ end_pos + 1 ] ) ) end_pos++;
  s = line( pos, end_pos );
  pos = end_pos + 1;
  return false;
}

bool parse_unit_def( partial_unit &punit ) {
  /* get name */
  string name;
  if( get_string( name ) ) {
    cerr << "parse_unit_def -- failed to parse name\n";
    return true;
  }
  
  /* get optional coefficient */
  string s;
  if( get_string( s ) ) {
    cerr << "parse_unit_def -- failed to parse def for \"" << name << "\"\n";
    return true;
  }

  punit.name = name;
  punit.coefficient = 1;
  punit.offset = 0;
  for( int i = 0 ; i < num_basic_units ; i++ ) {
    punit.c[i] = 0;
  }

  if( isdigit( s[0] ) || s[0] == '-' || s[0] == '+' || s[0] == '.' ) {
    /* insert e if necessary before - or + */
    int mpos, npos = 0, spos = 0;
    while( ( mpos = s.pos( '-', spos + 1 ) ) > 0 || 
	    ( npos = s.pos( '+', spos + 1 ) ) > 0 ) {
      if( mpos > 0 && npos > 0 ) 
	spos = min( mpos, npos );
      else 
	spos = max( mpos, npos );
      if( tolower( s[spos - 1 ] ) != 'e' )
	s = s.insert( "e", spos );
      npos = 0;
    }

    if( !sscanf( s, "%lf", &punit.coefficient ) ) {
      cerr << "parse_unit_def -- failed to parse coefficient for \"" << name 
	   << "\"\n";
      return true;
    }

    /* '|' used for division */
    int div_pos = s.pos( '|' );
    if( div_pos > 0 ) {
      double d;
      if( !sscanf( s(div_pos + 1, s.length() - 1 ), "%lf", &d ) ) {
	cerr << "parse_unit_def -- failed to parse coefficient for \"" << name 
	     << "\"\n";
      }
      punit.coefficient /= d;
    }
    
    if( get_string( s ) ) {
      return false;
    }
  }

  /* 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;

      npos = end_pos + 1;
      int nend = end_pos;
      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;
      }
      punit.add_pair( name, power * pow_mod );
    } else {
      return true;
    }
    if( pos < len ) {
      /* check for '*', '-', '/' */
      switch( s[pos] ) {
      case '*' : pow_mod = 1;
      case '-' : pos++; break;
      case '/' : pow_mod *= -1; pos++; break;
      default :
	if( !isalpha( s[pos] ) ) {
	  return true;
	}
      }
    }
  }
  
  return false;
}

ostream &operator<<( ostream &stream, const unit &U ) {
  stream << "unit \"" << U.name << "\" : \n";
  bool first = true;
  for( int i = 0 ; i < num_basic_units ; i++ ) {
    if( U.c[i] > 0 ) {
      if( first ) first = false; else stream << "*";
      stream << quantity_name[i];
    }
    if( U.c[i] > 1 ) stream << "^" << U.c[i];
  }
  if( first ) stream << "\t" << 1; else first = true;
  for( int i = 0 ; i < num_basic_units ; i++ ) {
    if( U.c[i] < 0 ) {
      if( first ) { first = false; stream << "/"; } else stream << "*";   
      stream << quantity_name[i];
    }
    if( U.c[i] < -1 ) stream << "^" << -U.c[i];
  }
  cerr << "\n" << U.coefficient << " * ";
  first = true;
  for( int i = 0 ; i < num_basic_units ; i++ ) {
    if( U.c[i] > 0 ) {
      if( first ) first = false; else stream << "*";
      stream << unit_name[i];
    }
    if( U.c[i] > 1 ) stream << "^" << U.c[i];
  }
  if( first ) stream << "\t" << 1; else first = true;
  for( int i = 0 ; i < num_basic_units ; i++ ) {
    if( U.c[i] < 0 ) {
      if( first ) { first = false; stream << "/"; } else stream << "*";   
      stream << unit_name[i];
    }
    if( U.c[i] < -1 ) stream << "^" << -U.c[i];
  }
  if( U.offset ) cerr << "\t+ " << U.offset;
  return stream << "\n";
}

ostream &operator<<( ostream &stream, const partial_unit &punit ) {
  stream << "partial unit \"" << punit.name << "\" : " <<  punit.coefficient
	 << " *\n\t";;
  bool first = true;
  for( int i = 0 ; i < num_basic_units ; i++ ) {
    if( punit.c[i] > 0 ) {
      if( first ) first = false; else stream << "*";
      stream <<  quantity_name[i];
    }
    if( punit.c[i] > 1 ) stream << "^" << punit.c[i];
  }
  if( first ) stream << "\t" << 1; else first = true;
  for( int i = 0 ; i < num_basic_units ; i++ ) {
    if( punit.c[i] < 0 ) {
      if( first ) { first = false; stream << "/"; } else stream << "*";   
      stream << quantity_name[i];
    }
    if( punit.c[i] < -1 ) stream << "^" << -punit.c[i];
  }
  if( !punit.unresolved.empty() ) {
    stream << "\n\tUnresolved :\n";
    unit_power_pair *up;
    forall( up,  punit.unresolved ) {
      stream << "\t" << up->name;
      if( up->power != 1 ) stream << "^" << up->power;
      stream << "\n";
    }
  }
  if( punit.offset ) cerr << "\t+ " << punit.offset;
  return stream << "\n";
}

int unit_block::load_unit_file( const char *fname ) {
  ifstream infile( fname );

  if( !infile ) {
    cerr << "unit_block::load_unit_file -- failed to open file \"" << fname
	 << "\"\n";
    return 0;
  }

  set_stream( infile );

  int num_undef = 0;
  list<partial_unit *> punit_list;
  bool used = true;
  partial_unit *p;
  while( !infile.eof() ) {
    bool gotline = false;
    while( !infile.eof() ) { 
      if( !get_line() ) {
	gotline = true;
	break;
      }
    }
    if( !gotline ) break;
    if( used ) {
      p = new partial_unit();
      used = false;
    }
    if( parse_unit_def( *p ) ) { 
      num_undef++;
      cerr << "unit_block::load_unit_file -- failed to parse unit at line " 
	   << line_num() << " of " << fname << "\n";
    } else {
      used = true;
      punit_list.append( p );
    }
  }
  if( !used ) delete( p );

  /* cycle through list until no progress made.
     on each cycle try to resolve any unresolved references by looking in
     unit dict.
     */
  int num_defined;
  do {
    num_defined = 0;
    list_item li = punit_list.first();
    while( li ) {
      partial_unit *punit = punit_list.inf( li );
      bool undef = false;
      list_item ur = punit->unresolved.first();
      while( ur ) {
	unit_power_pair *upp = punit->unresolved.inf( ur );
	double coeff;
	unit *U = lookup( upp->name, coeff );
	if( U ) { /* resolve */
	  for( int i = 0 ; i < num_basic_units ; i++ ) {
	    punit->c[i] += ( *U )[i] * upp->power;
	  }
	  double Ufactor = pow( coeff * U->Coeff(), upp->power );
	  punit->coefficient *= Ufactor;
	  	  
	  list_item urtmp = ur;
	  ur = punit->unresolved.succ( ur );
	  punit->unresolved.del_item( urtmp );
	} else { 
	  undef = true;
	  /* breaking on first undefined may mean less work in the end, 
	     but makes displaying of undefined labels harder */
	  /* break; */
	  ur = punit->unresolved.succ( ur );
	}
      }
      if( !undef ) {
	list_item litmp = li;
	li = punit_list.succ( li );
	punit_list.del_item( litmp );
	if( !define( punit->name, punit->coefficient, 
		     punit->offset, punit->c ) ) {
	  cerr << "unit_block::load_unit_file -- error trying to redefine \""
	       << punit->name << "\"\n";
	  num_undef++;
	} else num_defined++;
	delete( punit );
      } else {
	li = punit_list.succ( li );
      }
    }
  } while( num_defined );
  
  /* flag all with undefined references */
  num_undef += punit_list.size();
  if( num_undef ) {
    cerr << "unit_block::load_unit_file( \"" << fname << "\" ) -- " << num_undef
	 << " unresolved unit definitions :\n";
    partial_unit *punit;
    forall( punit, punit_list ) {
      cerr << *punit << "\n";
      delete( punit );
    }
  }
  set_stream();
  return num_undef;
}

