/*
 * Copyright Kay Stenschke
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.kstenschke.shifter.models.shiftable_types;

import com.kstenschke.shifter.utils.UtilsTextual;

/**
 * Roman number class
 */
public class RomanNumber {

    /**
     * @param  str      String to be checked
     * @return boolean  Does the given string represent a CSS length value?
     */
    public static boolean isRomanNumber(String str) {
        return UtilsTextual.containsOnly(str, new String[]{"I", "V", "X", "L", "C", "D", "M"});
    }

    /**
     * @param  value    String representing a roman numeral
     * @param  isUp     Shifting up or down?
     * @return String   Value shifted up or down by one
     */
    public String getShifted(String value, boolean isUp) {
        int intVal = new RomanNumeral(value).toInt();

        return 1 == intVal && !isUp
                ? value
                : new RomanNumeral(isUp ? intVal + 1 : intVal -1).toString();
    }

    /**
     * An object of type RomanNumeral is an integer between 1 and 3999.  It can
     * be constructed either from an integer or from a string that represents
     * a Roman numeral in this range.  The function toString() will return a
     * standardized Roman numeral representation of the number.  The function
     * toInt() will return the number as a value of type int.
     */
    class RomanNumeral {
        private final int num;   // The number represented by this Roman numeral.

         /* The following arrays are used by the toString() function to construct
            the standard Roman numeral representation of the number.  For each i,
            the number numbers[i] is represented by the corresponding string, letters[i].
         */

        private final int[]    numbers = { 1000,  900,  500,  400,  100,   90,
                50,   40,   10,    9,    5,    4,    1 };

        private final String[] letters = { "M",  "CM",  "D",  "CD", "C",  "XC",
                "L",  "XL",  "X",  "IX", "V",  "IV", "I" };

        /**
         * Constructor.  Creates the Roman number w/ the int value specified by the parameter.
         * Throws a NumberFormatException if arabic is not in the range 1 to 3999 inclusive.
         */
        RomanNumeral(int arabic) {
            if (arabic < 1) {
                throw new NumberFormatException("Value of RomanNumeral must be positive.");
            } else if (arabic > 3999) {
                throw new NumberFormatException("Value of RomanNumeral must be 3999 or less.");
            }

            num = arabic;
        }

        /*
         * Constructor.  Creates the Roman number w/ the given representation.
         * For example, RomanNumeral("xvii") is 17.  If the parameter is not a
         * legal Roman numeral, a NumberFormatException is thrown.  Both upper and
         * lower case letters are allowed.
         */
        RomanNumeral(String roman) {
            if (roman.length() == 0) {
                throw new NumberFormatException("An empty string does not define a Roman numeral.");
            }

            String romanUpper = roman.toUpperCase();

            int i      = 0;  // A position in the string, roman
            int arabic = 0;  // Arabic numeral equivalent of the part of the string that has been converted so far

            while (i < romanUpper.length()) {
                char letter = romanUpper.charAt(i);        // Letter at current position in string.
                int number = letterToNumber(letter);  // Numerical equivalent of letter.

                i++;  // Move on to next position in the string

                if (i == romanUpper.length()) {
                    // There is no letter in the string following the one we have just processed.
                    // So just add the number corresponding to the single letter to arabic.
                    arabic += number;
                } else {
                    // Look at the next letter in the string.  If it has a larger Roman numeral
                    // equivalent than number, then the two letters are counted together as
                    // a Roman numeral w/ value (nextNumber - number).
                    int nextNumber = letterToNumber(romanUpper.charAt(i));
                    if (nextNumber > number) {
                        // Combine the two letters to get one value, and move on to next position in string.
                        arabic += (nextNumber - number);
                        i++;
                    } else {
                        // Don't combine the letters.  Just add the value of the one letter onto the number.
                        arabic += number;
                    }
                }
            }

            if (arabic > 3999) {
                throw new NumberFormatException("Roman numeral must have value 3999 or less.");
            }

            num = arabic;
        }

        /**
         * Find the integer value of letter considered as a Roman numeral.
         * Throws NumberFormatException if letter is not a legal Roman numeral. The letter must be upper case.
         */
        private int letterToNumber(char letter) {
            switch (letter) {
                case 'I':
                    return 1;
                case 'V':
                    return 5;
                case 'X':
                    return 10;
                case 'L':
                    return 50;
                case 'C':
                    return 100;
                case 'D':
                    return 500;
                case 'M':
                    return 1000;
                default:
                    throw new NumberFormatException("Illegal character \"" + letter + "\" in Roman numeral");
            }
        }

        /**
         * Return the standard representation of this Roman numeral.
         */
        public String toString() {
            StringBuilder roman = new StringBuilder();
            // N represents the part of num that still has to be converted to Roman numeral representation.
            int nonRoman = num;
            for (int i = 0; i < numbers.length; i++) {
                while (nonRoman >= numbers[i]) {
                    roman.append(letters[i]);
                    nonRoman -= numbers[i];
                }
            }

            return roman.toString();
        }

        /**
         * Return the value of this Roman numeral as an int.
         */
        int toInt() {
            return num;
        }
    }
}

@@ -1454,16 +1454,17 @@
 intVal -
+ 
 1).toStr
@@ -1872,16 +1872,23 @@
  */%0A    
+static 
 class Ro

improve memory handling: make inner class static