
#include "roman_numeral.h"
#include <ctype.h>

// The algorithm here is simple.  It observes that each of the Roman
// letters MDCLXVI represents a unique positive integer.  Where another
// letter stands immediately to the right and the rightward letter
// represents a larger positive integer, then the present letter's
// integer is subtracted from the total; otherwise it is added.  For
// example, XIV or xiv represents +10-1+5 == 14.  If fed an arbitrary
// sequence of Roman digits, the algorithm returns a predictable though
// not necessarily sensical integer; for instance, on IXIIIVL it
// returns -1+10+1+1-1-5+50 == 55 (how a real Roman would have read such
// a numeral, one can only guess).  The algorithm does return the
// expected integer for any proper Roman numeral, and returns zero for
// the empty string.

int Util::roman_to_int( const char roman ) {
  switch ( roman ) {
    case 'I': case 'i': return    1; break;
    case 'V': case 'v': return    5; break;
    case 'X': case 'x': return   10; break;
    case 'L': case 'l': return   50; break;
    case 'C': case 'c': return  100; break;
    case 'D': case 'd': return  500; break;
    case 'M': case 'm': return 1000; break;
    default:
      if ( isspace( roman ) ) return 0;
      else throw Exc_roman_numeral();
  }
  return 0; // unreachable
}

int Util::roman_to_int( const char *roman ) {
  int total = 0;
  int prev  = 0;
  for ( ; *roman; ++roman ) {
    const int curr = roman_to_int( *roman );
    if ( curr ) {
      if ( prev < curr ) total -= prev << 1;
      total += prev = curr;
    }
  }
  // A slightly pedantic note on a small point of programming style:
  // Given the particular values assigned to the seven Roman-numeral
  // letters, the `total' can never actually come to less than zero.  It
  // could do so however if, for example, the values 4 and 6 were for
  // some reason assigned respectively to U and W; then, UVW would
  // represent -4-5+6 == -3.  This might surprise a caller who, quite
  // reasonably, expected a nonnegative return.  Admittedly, no one
  // seems likely to assign such values to the unused Roman letters
  // (technically U and W, like J and arguably Y and Z, are not
  // authentic Roman letters at all, but we digress), but for
  // generality's sake the code prohibits a negative return anyway.  The
  // point of programming style exposed herein is that code not follow
  // indirect reasoning through a subtle logical shortcut such as, in
  // this case, depending on `total' already to be
  // nonnegative---especially when the shortcut in fact cuts little from
  // the processing time.  It seems neater to bound `total' explicitly,
  // even though the bound were never exercised.
  return total >= 0 ? total : 0;
}

int Util::roman_to_int( const std::string &roman ) {
  return roman_to_int( roman.c_str() );
}

std::string Util::int_to_roman( int n, const Roman_case rcase ) {
  if ( n < 0 ) throw Exc_roman_numeral();
  std::string roman;
  {
    struct {
      bool operator()( int *const n, const int r ) const {
        if ( *n >= r ) {
          *n -= r;
          return true;
        }
        return false;
      }
    } reduce;
    while ( n > 0 ) {
      if      ( reduce( &n, 1000 ) ) roman += rcase ?  "m" :  "M";
      else if ( reduce( &n,  900 ) ) roman += rcase ? "cm" : "CM";
      else if ( reduce( &n,  500 ) ) roman += rcase ?  "d" :  "D";
      else if ( reduce( &n,  400 ) ) roman += rcase ? "cd" : "CD";
      else if ( reduce( &n,  100 ) ) roman += rcase ?  "c" :  "C";
      else if ( reduce( &n,   90 ) ) roman += rcase ? "xc" : "XC";
      else if ( reduce( &n,   50 ) ) roman += rcase ?  "l" :  "L";
      else if ( reduce( &n,   40 ) ) roman += rcase ? "xl" : "XL";
      else if ( reduce( &n,   10 ) ) roman += rcase ?  "x" :  "X";
      else if ( reduce( &n,    9 ) ) roman += rcase ? "ix" : "IX";
      else if ( reduce( &n,    5 ) ) roman += rcase ?  "v" :  "V";
      else if ( reduce( &n,    4 ) ) roman += rcase ? "iv" : "IV";
      else if ( reduce( &n,    1 ) ) roman += rcase ?  "i" :  "I";
      else throw Exc_roman_numeral(); // unreachable
    }
  }
  return roman;
}

