/* roman.c by Adam Rogoyski (apoc@laker.net) Temperanc on EFNet irc
 * Copyright (C) 1998 Adam Rogoyski
 * Converts Decimal numbers to Roman Numerals and Roman Numberals to
 * Decimals on the command line or in Interactive mode.
 * Uses an expanded Roman Numeral set to handle numbers up to 999999999
 * compile: gcc -o roman roman.c -O2
 * --- GNU General Public License Disclamer ---
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "wv.h"

#define I 1
#define V 5
#define X 10
#define L 50
#define C 100
#define D 500
#define M 1000
#define P 5000
#define Q 10000
#define R 50000
#define S 100000
#define T 500000
#define U 1000000
#define B 5000000
#define W 10000000
#define N 50000000
#define Y 100000000
#define Z 500000000

#define LARGEST 999999999


#include "roman.h"

#if 0
int
main (int argc, char **argv)
{
   char roman[81];
   int i = 0;
   long decimal = 0;
   if (argc < 2)
   {
      char *buf = wvMalloc (81);
      if (!buf)
      {
         wvError(("Not enough memory\n"));
         exit (EXIT_FAILURE);
      }
      printf ("Decimal <=> Roman Numberal converter by Adam Rogoyski\n");
      printf ("Enter Decimals or Romans to convert, quit to stop\n");
      printf ("Range: 1-999999999, I-ZYZWYUWSUQSMQCMXCIX\n\n==> ");
      fflush (stdout);
      memset (buf, 0, 81);
      while (strncasecmp(fgets(buf, 81, stdin) ? buf : "", "quit", 4))
      {
         if (!buf[0])
            exit (EXIT_SUCCESS);
         i = 0;
         while (isspace(buf[i]))
            i++;
         if (isdigit(buf[i]))
            puts (decimalToRoman(atoi(buf), roman));
         else
         {
            if (isupper(buf[i]))
            {
               chomp (buf);
               decimal = romanToDecimal(buf);
               if (decimal)
                  printf ("%ld\n", decimal);
            }
         }
         printf ("==> ");
         fflush (stdout);
         memset (buf, 0, 80);
      }
      wvFree (buf);
   }
   else
   {
      while (argc-- >= 2)
      {
         memset (roman, 0, 81);
         if (isdigit(**(argv + 1)))
         {
            decimal = atoi (*(argv + 1));
            puts (decimalToRoman(decimal, roman));
         }
         else
         {
            decimal = romanToDecimal(*(argv + 1));
            if (decimal)
               printf ("%ld\n", decimal);
         }
         argv++;
      }
   }
   return EXIT_SUCCESS;
}
#endif 

long
formString (char **r, long v, char a, char b)
{
   *(*r)++ = a;
   if (b)
      *(*r)++ = b;
   return v;
}

char * decimalToRoman (long decimal, char *roman)
{
   char *r = roman;
   memset (roman, 0, 81);
   r = roman;
   if (decimal > LARGEST || decimal < 1)
   {
      *r = '\0';
	  wvError(("roman broke\n"));
      return roman;
   }
   if (decimal >= Z)
      decimal -= formString (&r, Z, 'Z', '\0');
   if (decimal >= Z - Y)
      decimal -= formString (&r, Z - Y, 'Y', 'Z');
   while (decimal >= Y)
      decimal -= formString (&r, Y, 'Y', '\0');
   if (decimal >= Y - W)
      decimal -= formString (&r, Y - W, 'W', 'Y');
   if (decimal >= N)
      decimal -= formString (&r, N, 'N', '\0');
   if (decimal >= N - W)
      decimal -= formString (&r, N - W, 'W', 'N');
   while (decimal >= W)
      decimal -= formString (&r, W, 'W', '\0');
   if (decimal >= W - U)
      decimal -= formString (&r, W - U, 'U', 'W');
   if (decimal >= B)
      decimal -= formString (&r, B, 'B', '\0');
   if (decimal >= B - U)
      decimal -= formString (&r, B - U, 'U', 'B');
   while (decimal >= U)
      decimal -= formString (&r, U, 'U', '\0');
   if (decimal >= U - S)
      decimal -= formString (&r, U - S, 'S', 'U');
   if (decimal >= T)
      decimal -= formString (&r, T, 'T', '\0');
   if (decimal >= T - S)
      decimal -= formString (&r, T - S, 'S', 'T');
   while (decimal >= S)
      decimal -= formString (&r, S, 'S', '\0');
   if (decimal >= S - Q)
      decimal -= formString (&r, S - Q, 'Q', 'S');
   if (decimal >= R)
      decimal -= formString (&r, R, 'R', '\0');
   if (decimal >= R - Q)
      decimal -= formString (&r, R - Q, 'Q', 'R');
   while (decimal >= Q)
      decimal -= formString (&r, Q, 'Q', '\0');
   if (decimal >= Q - M)
      decimal -= formString (&r, Q - M, 'M', 'Q');
   if (decimal >= P)
      decimal -= formString (&r, P, 'P', '\0');
   if (decimal >= P - M)
      decimal -= formString (&r, P - M, 'M', 'P');
   while (decimal >= M)
      decimal -= formString (&r, M, 'M', '\0');
   if (decimal >= M - C)
      decimal -= formString (&r, M - C, 'C', 'M');
   if (decimal >= D)
      decimal -= formString (&r, D, 'D', '\0');
   if (decimal >= D - C)
      decimal -= formString (&r, D - C, 'C', 'D');
   while (decimal >= C)
      decimal -= formString (&r, C, 'C', '\0');
   if (decimal >= C - X)
      decimal -= formString (&r, C - X, 'X', 'C');
   if (decimal >= L)
      decimal -= formString (&r, L, 'L', '\0');
   if (decimal >= L - X)
      decimal -= formString (&r, L - X, 'X', 'L');
   while (decimal >= X)
      decimal -= formString (&r, X, 'X', '\0');
   switch ((int) decimal)
   {
      case 9:
         *r++ = 'I';
         *r++ = 'X';
         break;
      case 8:
         *r++ = 'V';
         *r++ = 'I';
         *r++ = 'I';
         *r++ = 'I';
         break;
      case 7:
         *r++ = 'V';
         *r++ = 'I';
         *r++ = 'I';
         break;
      case 6:
         *r++ = 'V';
         *r++ = 'I';
         break;
      case 4:
         *r++ = 'I';
      case 5:
         *r++ = 'V';
         break;
      case 3:
         *r++ = 'I';
      case 2:
         *r++ = 'I';
      case 1:
         *r++ = 'I';
         break;
   }
   return roman;
}


long
romanToDecimal (char *roman)
{
   long decimal = 0;
   for (; *roman; roman++)
   {
      /* Check for four of a letter in a fow */
      if ((*(roman + 1) && *(roman + 2) && *(roman + 3))
         && (*roman == *(roman + 1))
         && (*roman == *(roman + 2))
         && (*roman == *(roman + 3)))
         return 0;
      /* Check for two five type numbers */
      if (  ((*roman == 'V') && (*(roman + 1) == 'V'))
         || ((*roman == 'L') && (*(roman + 1) == 'L'))
         || ((*roman == 'D') && (*(roman + 1) == 'D'))
         || ((*roman == 'P') && (*(roman + 1) == 'P'))
         || ((*roman == 'R') && (*(roman + 1) == 'R'))
         || ((*roman == 'T') && (*(roman + 1) == 'T'))
         || ((*roman == 'B') && (*(roman + 1) == 'B'))
         || ((*roman == 'N') && (*(roman + 1) == 'N'))
         || ((*roman == 'Z') && (*(roman + 1) == 'Z')))
         return 0;
      /* Check for two lower characters before a larger one */
      if ((value(*roman) == value(*(roman + 1))) && (*(roman + 2))
         && (value(*(roman + 1)) < value(*(roman + 2))))
         return 0;
      /* Check for the same character on either side of a larger one */
      if ((*(roman + 1) && *(roman + 2)) 
         && (value(*roman) == value(*(roman + 2)))
         && (value(*roman) < value(*(roman + 1))))
         return 0;
      /* Check for illegal nine type numbers */
      if (!strncmp(roman, "LXL", 3) || !strncmp(roman, "DCD", 3)
       || !strncmp(roman, "PMP", 3) || !strncmp(roman, "RQR", 3)
       || !strncmp(roman, "TST", 3) || !strncmp(roman, "BUB", 3)
       || !strncmp(roman, "NWN", 3) || !strncmp(roman, "VIV", 3))
         return 0;
      if (value(*roman) < value(*(roman + 1)))
      {
         /* check that subtracted value is at least 10% larger, 
            i.e. 1990 is not MXM, but MCMXC */
         if ((10 * value(*roman)) < value(*(roman + 1)))
            return 0;
         /* check for double subtraction, i.e. IVX */
         if (value(*(roman + 1)) <= value(*(roman + 2)))
            return 0;
         /* check for subtracting by a number starting with a 5
            ie.  VX, LD LM */
         if (*roman == 'V' || *roman == 'L' || *roman == 'D'
          || *roman == 'P' || *roman == 'R' || *roman == 'T'
          || *roman == 'B' || *roman == 'N')
            return 0;
         decimal += value (*(roman + 1)) - value (*roman);
         roman++;
      }
      else
      {
         decimal += value (*roman);
      }
   }
   return decimal;
}


long
value (char c)
{
   switch (c)
   {
      case 'I':
         return I;
      case 'V':
         return V;
      case 'X':
         return X;
      case 'L':
         return L;
      case 'C':
         return C;
      case 'D':
         return D;
      case 'M':
         return M;
      case 'P':
         return P;
      case 'Q':
         return Q;
      case 'R':
         return R;
      case 'S':
         return S;
      case 'T':
         return T;
      case 'U':
         return U;
      case 'B':
         return B;
      case 'W':
         return W;
      case 'N':
         return N;
      case 'Y':
         return Y;
      case 'Z':
         return Z;
      default:
         return 0;
   }
}


/* chomp carriage return off end of string */
char *
chomp (char *str)
{
   static int i = 0;
   i = 0;
   while (*(str + i))
   {
      if ((*(str + i) == '\n') || (*(str + i) == '\r')
         || (*(str + i) == '\0'))
      {
         *(str + i) = 0;
         break;
      }
      else
         i++;
   }
   return (str - i);
}
