import java.io.*;
import java.math.BigInteger;
import static java.math.BigInteger.*;
import static java.lang.Math.*;

public class BigOpera
{
  static String dirPath = "C:\\jj\\work\\results\\";
  static String fileName = "big_opera.html";

  static int powerLimit = 1000000;             // limit at A^powerLimit
  static String[] operators = { "+", "*", "^", "^^", "^^^" };
  
  static int formatCols = 50;
  static int formatRows = 20;
  static int boxWidth = 500;      // width of HTML <p> element
  static int boxHeight = 250;     // change height of <p> overflow box here
                               
  static int radix = 10;                   // default radix..
  static boolean showRadix = false;        // ..is not shown in output
  
  static OperaAlphabet alpha;              // alphabet
  static boolean showAlphabet = false;     // another mode if radix arg <= 0 
  
/** The command line takes input like this:
  * 
  * BigPower A B Op radix
  * 
  * where A and B are integers Op is an operator index of [ +, *, ^, ^^, ^^^ ],
  * and the radix is an optional number base in the range 2 - 36, 
  * or an alphabet -number or a custom alphabet
  *
  * e.g. 20^3 in base 16, input: java BigOpera 20 3 2 16, output: 1f40
  * input: java BigOpera 26 0 0 -2, output: "YZ" (Latin reversed alphabet: base 26)
  * input: java BigOpera 3 3 3 " 'Th'EOS", output: "ThSSSSThSETh SES SSEE" (base 5)
  * 
  * Preferably recompile your own version of the program suited to your needs.
  */
  public static void main(String[] args)
  {
    // Set non-existent args to 0, as adding zero makes no change (?!)
    if(args.length == 0)
      args[0] = "0";
    if(args.length <= 1)
      args[1] = "0";
    if(args.length <= 2)
      args[2] = "0";
    
    // Set radix
    if(args.length == 4)
      setRadixOrAlphabet(args[3]);
    
    // Assign variables
    BigInteger A = new BigInteger(args[0]);   // PP BigDecimal implementation to follow?
    int B = Integer.parseInt(args[1]);
    int Op = Integer.parseInt(args[2]);

    printResults(A, B, Op);
  }
  
  static void printResults(BigInteger A, int B, int Op)
  {
    String alphaStr = "";
    String baseStr = "";
    String calcuStr = "";
    
    if(showAlphabet)
      alphaStr = alpha.toString();
    if(showRadix)
      baseStr = "base " + radix;

    calcuStr = "Opera(" + A + "," + B + "," + Op + ") = " + A + operators[Op] + B + " = ";
    
    BigInteger result = bigOpera(A, B, Op);
    
    String output = "ERROR";
    if(showAlphabet)
      output = alpha.translate(result);
    else if(radix >= 2)                     // also if not set and radix is default 10
      output = result.toString(radix);
    
    String quot = (showAlphabet ? "\"" : "");
    
    // Output to console
    System.out.println("\n\r\t" + 
                       alphaStr + (showAlphabet ? "\n\r\t" : "") + 
                       baseStr + (showRadix ? "\n\r\t" : "") + 
                       calcuStr + "\n\r" + 
                       quot + output + (showAlphabet ? "" : ".") + quot);
    
    // Output to HTML file
    try
    {
      File dir = new File(dirPath);
      if(!dir.exists())
        dir.mkdirs();
      File file = new File(dir, fileName);
      boolean noHTML = file.createNewFile();
      
      PrintWriter out = new PrintWriter(new FileWriter(file, true));
      
      if(showAlphabet) {
        int webLineLength = (int)Math.round(formatCols*.7);
        alphaStr = alpha.toString(webLineLength);
        
        // Preformat big number output with BR's inbetween multi-letters!
        output = alpha.translate(result, formatCols);
      }
      
      int bigLen = output.length();
      
      String widthStyle = "width:" + boxWidth + "px; ";
      String heightStyle = "";
      if(bigLen > formatCols*formatRows)
        heightStyle = "height:" + boxHeight + "px; ";
      
      if(noHTML) {
        out.println("<html><head><title>Big Opera Results</title>");
        out.println("<body>");
      }
      out.println("<div style=\"text-align:center;\">");
      
      out.println("<div style=\"" + widthStyle + "font:bold 13px 'Courier New', monospace;");
      out.println("     text-align:left; padding-left:10px;");
      out.println("     margin-left:auto; margin-right:auto; margin-top:30px;\">");
      if(showAlphabet)
        out.println("<pre style=\"margin-bottom:8px;\">" + alphaStr + "</pre>");
      if(showRadix)
        out.println(baseStr + "<br />");
      out.println(calcuStr + "</div>");
      
      out.println("<pre style=\"" + widthStyle + heightStyle + "overflow:auto;");
      out.println("     background:black; color:white; font:11px 'Courier New', monospace;");
      out.println("     text-align:left; padding:15px 5px 10px 10px;");
      out.println("     margin-left:auto; margin-right:auto; margin-bottom:30px;\">");
      
      if(bigLen < formatCols/2)     // small lines in big font
        out.println(quot + "<big>" + output + ".</big>" + quot);
      else
      { // Format big lines - PP! Hanging in this loop is dangerous for harddisk PP!
        for(int start = 0, end = 1; start < bigLen; start = end)
        {
          if(showAlphabet)
            end = output.indexOf(OperaAlphabet.BR, start);     // next <br />
          else
            end = start + formatCols;
          
          if(start == end)
            throw new IOException("Hanging in the loop");
          
          if(end == -1 || end >= bigLen)                  // last row
          {                                               // print last letters
            out.println(output.substring(start, bigLen) + ".");
            break;                                        // leave loop
          }
          
          if(showAlphabet)
          {
            end += OperaAlphabet.BR.length();             // end index +6
            out.print(output.substring(start, end));
          }
          else
            out.println(output.substring(start, end));
        }
      }
      out.println("</pre></div>");
      out.close();
    }
    catch(IOException e)
    {
      System.err.println(e);
    }
  }
  
  // Sets radix and flags to show radix
  public static void setRadixOrAlphabet(String arg)
  {
    int r = 1;                       // 1 does nothing
    if(arg.length() < 3)             // base 2 - 36 supported
      r = Integer.parseInt(arg);
    else {                           // else take it as an alphabet string
      alpha = new OperaAlphabet(arg);
      showAlphabet = true;
    }
    
    if(r >= 2)
      setRadix(r);
    if(r <= 0) {           // another mode if radix arg <= 0
      r = -r;
      if(OperaAlphabet.alphabetExists(r)) {
        alpha = new OperaAlphabet(r);
        showAlphabet = true;
      }
    }
    // Also show radix (number of chars) if we've got an alphabet
    if(showAlphabet)
      setRadix(alpha.length());
  } 
  
  public static void setRadix(int num)
  {
    radix = num;
    showRadix = true;
  }
  
  public static BigInteger bigOpera(BigInteger A, int B, int Op)
  {
    BigInteger result;
    String powerExceptionInfo = "\nABORT CALCULATION as the exponent's limit was reached at:\n";
    
    switch(Op)
    {
      case 0:                               // +
        result = A.add(valueOf(B));
        break;
        
      case 1:                               // *
        result = A.multiply(valueOf(B));
        break;
        
      case 2:                               // ^
        if(B > powerLimit)
          throw new ArithmeticException(powerExceptionInfo + B);
        result = A.pow(B);
        break;
        
      case 3:                               // ^^
        if(B <= 0)
          throw new ArithmeticException("Does not support exponent: " + B);
        
        result = A;
        for(int i = 1; i < B; i++) 
        {
          if(result.compareTo(valueOf(powerLimit)) == 1)
            throw new ArithmeticException(powerExceptionInfo + result);
          
          int b = result.intValue();
          result = A.pow(b);
        }
        break;
        
      case 4:                               // ^^^
        result = superOpera(A, B, Op);
        break;
        
      default:
        throw new ArithmeticException("Does not support operator: " + Op);
    }
    
    return result;
  }
    
  private static BigInteger superOpera(BigInteger A, int B, int Op)
  {
    if(Op <= 3)
      return bigOpera(A, B, Op);
      
    BigInteger result = A;
    for(int i = 1; i < B; i++)
    {
      int b = result.intValue();
      result = superOpera(A, b, Op-1);
    }
    return result; 
  }
}
