package com.rox.emu.processor.mos6502.op.util;

import com.rox.emu.UnknownOpCodeException;
import com.rox.emu.processor.mos6502.op.AddressingMode;

import java.util.Arrays;

/**
 * Utility for converting internal opcode representation names to human readable descriptions
 *
 * e.g.  OP_ADC_I  ->  "ADC (Immediate)"
 *
 * @author Ross Drew
 */
public class OpCodeConverter {
    //Internal representation details
    private static final String SEPARATOR = "_";
    private static final int OP_DELIMITER = 0;
    private static final int OP_CODE = 1;
    private static final int OP_ADD = 2;
    private static final int OP_I = 3;

    //Addressing modes XXX These can be removed once toDescription() is switched over
    public static final String ADDR_IMP = "Implied";
    public static final String ADDR_I = "Immediate";
    public static final String ADDR_A = "Accumulator";
    public static final String ADDR_Z = "Zero Page";
    public static final String ADDR_ABS = "Absolute";
    public static final String ADDR_IND = "Indirect";

    //Indexing modes
    public static final String INDEX_X = " [X]";
    public static final String INDEX_Y = " [Y]";

    public static String getOpCode(String internalOpCodeName){
        final String tokens[] = internalOpCodeName.split(SEPARATOR);
        return tokens[OP_CODE];
    }

    public static AddressingMode getAddressingMode(String internalOpCodeName){
        final String tokens[] = internalOpCodeName.split(SEPARATOR);
        if (tokens.length < 3)
            return AddressingMode.IMPLIED;

        final String addressingModeDescriptor = tokens[OP_ADD];

        switch (addressingModeDescriptor){
            case "I": return AddressingMode.IMMEDIATE;
            case "A": return AddressingMode.ACCUMULATOR;
            case "Z":
                switch ((tokens.length <= OP_I) ? "" : tokens[OP_I]){
                    case "IX": return AddressingMode.ZERO_PAGE_X;
                    case "IY": return AddressingMode.ZERO_PAGE_Y;
                    default: return AddressingMode.ZERO_PAGE;
                }
            case "ABS":
                switch ((tokens.length <= OP_I) ? "" : tokens[OP_I]){
                    case "IX": return AddressingMode.ABSOLUTE_X;
                    case "IY": return AddressingMode.ABSOLUTE_Y;
                    default: return AddressingMode.ABSOLUTE;
                }
            case "IND":
                switch ((tokens.length <= OP_I) ? "" : tokens[OP_I]){
                    case "IX": return AddressingMode.INDIRECT_X;
                    case "IY": return AddressingMode.INDIRECT_Y;
                    default: return AddressingMode.INDIRECT;
                }
            default:
                throw new UnknownOpCodeException("Unrecognised addressing mode " + addressingModeDescriptor, internalOpCodeName);
        }
    }

    public static String toDescription(String internalOpCodeName){
        if (internalOpCodeName != null && !internalOpCodeName.isEmpty()){
            String tokens[] = internalOpCodeName.split(SEPARATOR);
            if (!tokens[OP_DELIMITER].equalsIgnoreCase("OP"))
                throw new UnknownOpCodeException("Opcode not properly delimited", internalOpCodeName);
            return (getOpCode(tokens) + getAddressingMode(tokens)); //XXX Change to use the new style
        }else{
            throw new UnknownOpCodeException("Empty Opcode", internalOpCodeName);
        }
    }

    private static String getOpCode(String[] t){
        return t[OP_CODE];
    }

    private static String getAddressingMode(String[] t) {
        String addressingModeDescription = " (";

        if (t.length > OP_ADD){
            switch (t[OP_ADD]){
                case "I":
                    addressingModeDescription += ADDR_I;
                    break;
                case "A":
                    addressingModeDescription += ADDR_A;
                    break;
                case "Z":
                    addressingModeDescription += ADDR_Z;
                    break;
                case "ABS":
                    addressingModeDescription += ADDR_ABS;
                    break;
                case "IND":
                    addressingModeDescription += ADDR_IND;
                    break;
                default:
                    throw new UnknownOpCodeException("Unrecognised addressing mode " + t[OP_ADD], Arrays.toString(t));
            }
            addressingModeDescription += getIndexingMode(t);
        }else{
            addressingModeDescription += ADDR_IMP;
        }
        return addressingModeDescription + ")";
    }

    private static String getIndexingMode(String[] t) {
        String indexingModeDescription = "";
        if (t.length > OP_I){
            switch (t[OP_I]){
                case "IX":
                    indexingModeDescription += INDEX_X;
                    break;
                case "IY":
                    indexingModeDescription += INDEX_Y;
                    break;
                default:
                    throw new UnknownOpCodeException("Unrecognised indexing mode " + t[OP_ADD] + " in " + Arrays.toString(t), Arrays.toString(t));
            }
        }
        return indexingModeDescription;
    }

}

@@ -1613,16 +1613,95 @@
 _ADD%5D;%0A%0A
+        final String indexToken = (tokens.length %3C= OP_I) ? %22%22 : tokens%5BOP_I%5D;%0A
         
@@ -1889,273 +1889,262 @@
     
-switch ((tokens.length %3C= OP_I) ? %22%22 : tokens%5BOP_I%5D)%7B%0A                    case %22IX%22: return AddressingMode.ZERO_PAGE_X;%0A                    case %22IY%22: return AddressingMode.ZERO_PAGE_Y;%0A                    default: return AddressingMode.ZERO_PAGE;%0A                %7D
+return withIndexing(AddressingMode.ZERO_PAGE, indexToken);%0A            case %22ABS%22:%0A                return withIndexing(AddressingMode.ABSOLUTE, indexToken);%0A            case %22IND%22:%0A                return withIndexing(AddressingMode.INDIRECT, indexToken);
 %0A   
@@ -2148,34 +2148,31 @@
             
-case %22ABS%22
+default
 :%0A          
@@ -2181,125 +2181,120 @@
     
-switch ((tokens.length %3C= OP_I) ? %22%22 : tokens%5BOP_I%5D)%7B%0A                    case %22IX%22: return AddressingMode.ABSOLUTE_X
+throw new UnknownOpCodeException(%22Unrecognised addressing mode %22 + addressingModeDescriptor, internalOpCodeName)
 ;%0A  
@@ -2303,235 +2303,152 @@
     
+%7D%0A
     
-        case %22IY%22: return AddressingMode.ABSOLUTE_Y;%0A                    default: return AddressingMode.ABSOLUTE;%0A                %7D%0A            case %22IND%22:%0A                switch ((tokens.length %3C= OP_I) ? %22%22 : tokens%5BOP_I%5D
+%7D%0A%0A    public static AddressingMode withIndexing(final AddressingMode addressingMode, final String indexToken)%7B%0A        switch (indexToken
 )%7B%0A 
@@ -2454,32 +2454,24 @@
             
- 
-       
 case %22IX%22: r
@@ -2468,33 +2468,33 @@
 se %22IX%22: return 
-A
+a
 ddressingMode.IN
@@ -2495,18 +2495,18 @@
 ode.
-INDIRECT_X
+xIndexed()
 ;%0A  
@@ -2511,32 +2511,24 @@
             
-        
 case %22IY%22: r
@@ -2525,33 +2525,33 @@
 se %22IY%22: return 
-A
+a
 ddressingMode.IN
@@ -2552,248 +2552,62 @@
 ode.
-INDIRECT_Y;%0A                    default: return AddressingMode.INDIRECT;%0A                %7D%0A            default:%0A                throw new UnknownOpCodeException(%22Unrecognised addressing mode %22 + addressingModeDescriptor, internalOpCodeName)
+yIndexed();%0A            default: return addressingMode
 ;%0A  

Extracting out common code and complexity