package tv.porst.swfretools.dissector.gui.main.panels;

import tv.porst.splib.binaryparser.AsciiString;
import tv.porst.swfretools.parser.actions.as2.*;
import tv.porst.swfretools.parser.structures.ActionList;
import tv.porst.swfretools.parser.structures.StringList;

/**
 * Contains functions that turn ActionScript 2 code into a string that can
 * be displayed to the user.
 */
public final class AS2CodePrinter {

	/**
	 * Adds a simple one-mnemonic instruction to the output.
	 * 
	 * @param sb The string builder the output is appended to.
	 * @param mnemonic The instruction mnemonic.
	 */
	private static void add(final StringBuilder sb, final String mnemonic) {
		sb.append(mnemonic);
	}

	/**
	 * Adds an instruction with one argument to the output.
	 * 
	 * @param sb The string builder the output is appended to.
	 * @param mnemonic The instruction mnemonic.
	 * @param value The instruction argument value.
	 */
	private static void add(final StringBuilder sb, final String mnemonic, final long value) {
		sb.append(String.format("%s %d", mnemonic, value));
	}

	/**
	 * Adds an instruction with one argument to the output.
	 * 
	 * @param sb The string builder the output is appended to.
	 * @param mnemonic The instruction mnemonic.
	 * @param value The instruction argument value.
	 */
	private static void add(final StringBuilder sb, final String mnemonic, final String value) {
		sb.append(String.format("%s %s", mnemonic, value));
	}

	/**
	 * Adds an instruction with two arguments to the output.
	 * 
	 * @param sb The string builder the output is appended to.
	 * @param mnemonic The instruction mnemonic.
	 * @param value1 The first instruction argument value.
	 * @param value2 The second instruction argument value.
	 */
	private static void add(final StringBuilder sb, final String mnemonic, final String value1, final String value2) {
		sb.append(String.format("%s %s", mnemonic, value1, value2));
	}

	/**
	 * Adds an instruction with one hexadecimal argument to the output.
	 * 
	 * @param sb The string builder the output is appended to.
	 * @param mnemonic The instruction mnemonic.
	 * @param value The instruction argument value.
	 */
	private static void addHex(final StringBuilder sb, final String mnemonic, final long value) {
		sb.append(String.format("%s %X", mnemonic, value));
	}

	/**
	 * Adds an instruction to the output.
	 * 
	 * @param sb The string builder the output is appended to.
	 * @param instruction The instruction to add to the output.
	 * @param firstOffset The offset of the first instruction in the script.
	 */
	private static void addInstructionText(final StringBuilder sb, final Action instruction, final int firstOffset) {

		new AS2Visitor() {

			@Override
			protected void visit(final ActionAdd instruction) {
				add(sb, "Add");
			}

			@Override
			protected void visit(final ActionAdd2 instruction) {
				add(sb, "Add2");
			}

			@Override
			protected void visit(final ActionAnd instruction) {
				add(sb, "And");
			}

			@Override
			protected void visit(final ActionAsciiToChar instruction) {
				add(sb, "ASCIIToChar");
			}

			@Override
			protected void visit(final ActionBitAnd instruction) {
				add(sb, "BitAnd");
			}

			@Override
			protected void visit(final ActionBitLShift instruction) {
				add(sb, "BitLShift");
			}

			@Override
			protected void visit(final ActionBitOr instruction) {
				add(sb, "BitOr");
			}

			@Override
			protected void visit(final ActionBitRShift instruction) {
				add(sb, "BitRShift");
			}

			@Override
			protected void visit(final ActionBitURShift instruction) {
				add(sb, "BitURShift");
			}

			@Override
			protected void visit(final ActionBitXor instruction) {
				add(sb, "BitXor");
			}

			@Override
			protected void visit(final ActionCall instruction) {
				add(sb, "Call");
			}

			@Override
			protected void visit(final ActionCallFunction instruction) {
				add(sb, "CallFunction");
			}

			@Override
			protected void visit(final ActionCallMethod instruction) {
				add(sb, "CallMethod");
			}

			@Override
			protected void visit(final ActionCastOp instruction) {
				add(sb, "CastOp");
			}

			@Override
			protected void visit(final ActionCharToAscii instruction) {
				add(sb, "ChartToASCII");
			}

			@Override
			protected void visit(final ActionCloneSprite instruction) {
				add(sb, "CloneSprite");
			}

			@Override
			protected void visit(final ActionConstantPool instruction) {
				add(sb, "ConstantPool");

				final StringList strings = instruction.getConstantPool();

				if (strings.size() != 0) {
					sb.append('\n');
				}

				for (int i = 0; i < strings.size(); i++) {
					final AsciiString string = strings.get(i);
					sb.append(String.format("                      %03d: \"%s\"", i, string.value()));

					if (i != strings.size() - 1) {
						sb.append('\n');
					}
				}
			}

			@Override
			protected void visit(final ActionDecrement instruction) {
				add(sb, "Decrement");
			}

			@Override
			protected void visit(final ActionDefineFunction instruction) {
				add(sb, "DefineFunction");
				add(sb, " ");
				add(sb, instruction.getFunctionName().value());
				sb.append('\n');
				sb.append("{\n");
				sb.append(getCodeText(instruction.getCode(), "    "));
				sb.append("\n}");
			}

			@Override
			protected void visit(final ActionDefineFunction2 instruction) {
				add(sb, "DefineFunction2");
				add(sb, " ");
				add(sb, instruction.getFunctionName().value());
				sb.append('\n');
				sb.append("{\n");
				sb.append(getCodeText(instruction.getActionList(), "    "));
				sb.append("\n}");
			}

			@Override
			protected void visit(final ActionDefineLocal instruction) {
				add(sb, "DefineLocal");
			}

			@Override
			protected void visit(final ActionDefineLocal2 instruction) {
				add(sb, "DefineLocal2");
			}

			@Override
			protected void visit(final ActionDelete instruction) {
				add(sb, "Visit");
			}

			@Override
			protected void visit(final ActionDelete2 instruction) {
				add(sb, "Delete2");
			}

			@Override
			protected void visit(final ActionDivide instruction) {
				add(sb, "Divide");
			}

			@Override
			protected void visit(final ActionEndDrag instruction) {
				add(sb, "EndDrag");
			}

			@Override
			protected void visit(final ActionEnumerate instruction) {
				add(sb, "Enumerate");
			}

			@Override
			protected void visit(final ActionEnumerate2 instruction) {
				add(sb, "Enumerate2");
			}

			@Override
			protected void visit(final ActionEquals instruction) {
				add(sb, "Equals");
			}

			@Override
			protected void visit(final ActionEquals2 instruction) {
				add(sb, "Equals2");
			}

			@Override
			protected void visit(final ActionExtends instruction) {
				add(sb, "Extends");
			}

			@Override
			protected void visit(final ActionGetMember instruction) {
				add(sb, "GetMember");
			}

			@Override
			protected void visit(final ActionGetProperty instruction) {
				add(sb, "GetProperty");
			}

			@Override
			protected void visit(final ActionGetTime instruction) {
				add(sb, "GetTime");
			}

			@Override
			protected void visit(final ActionGetURL instruction) {
				add(sb, "GetURL", instruction.getUrlString().value(), instruction.getTargetString().value());
			}

			@Override
			protected void visit(final ActionGetURL2 instruction) {
				add(sb, "GetURL2");
			}

			@Override
			protected void visit(final ActionGetVariable instruction) {
				add(sb, "GetVariable");
			}

			@Override
			protected void visit(final ActionGotoFrame instruction) {
				add(sb, "GotoFrame", instruction.getFrame().value());
			}

			@Override
			protected void visit(final ActionGotoFrame2 instruction) {
				add(sb, "GotoFrame2");
			}

			@Override
			protected void visit(final ActionGotoLabel instruction) {
				add(sb, "GotoLabel", instruction.getLabel().value());
			}

			@Override
			protected void visit(final ActionGreater instruction) {
				add(sb, "Greater");
			}

			@Override
			protected void visit(final ActionIf instruction) {
				addHex(sb, "If", (instruction.getBitPosition() - firstOffset) / 8 + instruction.getBitLength() / 8 + instruction.getBranchOffset().value());
			}

			@Override
			protected void visit(final ActionImplementsOp instruction) {
				add(sb, "ImplementsOp");
			}

			@Override
			protected void visit(final ActionIncrement instruction) {
				add(sb, "Increment");
			}

			@Override
			protected void visit(final ActionInitArray instruction) {
				add(sb, "InitArray");
			}

			@Override
			protected void visit(final ActionInitObject instruction) {
				add(sb, "InitObject");
			}

			@Override
			protected void visit(final ActionInstanceOf instruction) {
				add(sb, "InstanceOf");
			}

			@Override
			protected void visit(final ActionJump instruction) {
				addHex(sb, "Jump", (instruction.getBitPosition() - firstOffset) / 8 + instruction.getBitLength() / 8 + instruction.getBranchOffset().value());
			}

			@Override
			protected void visit(final ActionLess instruction) {
				add(sb, "Less");
			}

			@Override
			protected void visit(final ActionLess2 instruction) {
				add(sb, "Less2");
			}

			@Override
			protected void visit(final ActionMBAsciiToChar instruction) {
				add(sb, "MBASCIIToChar");
			}

			@Override
			protected void visit(final ActionMBCharToAscii instruction) {
				add(sb, "MBCharToASCII");
			}

			@Override
			protected void visit(final ActionMBStringExtract instruction) {
				add(sb, "MBStringExtract");
			}

			@Override
			protected void visit(final ActionMBStringLength instruction) {
				add(sb, "MBStringLength");
			}

			@Override
			protected void visit(final ActionModulo instruction) {
				add(sb, "Modulo");
			}

			@Override
			protected void visit(final ActionMultiply instruction) {
				add(sb, "Multiply");
			}

			@Override
			protected void visit(final ActionNewMethod instruction) {
				add(sb, "NewMethod");
			}

			@Override
			protected void visit(final ActionNewObject instruction) {
				add(sb, "NewObject");
			}

			@Override
			protected void visit(final ActionNextFrame instruction) {
				add(sb, "NextFrame");
			}

			@Override
			protected void visit(final ActionNot instruction) {
				add(sb, "Not");
			}

			@Override
			protected void visit(final ActionOr instruction) {
				add(sb, "Or");
			}

			@Override
			protected void visit(final ActionPlay instruction) {
				add(sb, "Play");
			}

			@Override
			protected void visit(final ActionPop instruction) {
				add(sb, "Pop");
			}

			@Override
			protected void visit(final ActionPreviousFrame instruction) {
				add(sb, "PreviousFrame");
			}

			@Override
			protected void visit(final ActionPush instruction) {
				add(sb, "Push");

				final PushedValueList strings = instruction.getPushedValueList();

				if (strings.size() != 0) {
					sb.append('\n');
				}

				for (int i = 0; i < strings.size(); i++) {
					final PushedValue<?> string = strings.get(i);

					if (string instanceof PushedBoolean) {
						sb.append(String.format("                      %03d: %b", i, ((PushedBoolean)string).getValue().value()));
					}
					else if (string instanceof PushedConstant16) {
						sb.append(String.format("                      %03d: constant : %d", i, ((PushedConstant16)string).getValue().value()));
					}
					else if (string instanceof PushedConstant8) {
						sb.append(String.format("                      %03d: constant : %d", i, ((PushedConstant8)string).getValue().value()));
					}
					else if (string instanceof PushedDouble) {
						sb.append(String.format("                      %03d: %f", i, ((PushedDouble)string).getValue().value()));
					}
					else if (string instanceof PushedFloat) {
						sb.append(String.format("                      %03d: %f", i, ((PushedFloat)string).getValue().value()));
					}
					else if (string instanceof PushedInteger) {
						sb.append(String.format("                      %03d: %d", i, ((PushedInteger)string).getValue().value()));
					}
					else if (string instanceof PushedNull) {
						sb.append(String.format("                      %03d: null", i));
					}
					else if (string instanceof PushedRegisterNumber) {
						sb.append(String.format("                      %03d: r%d", i, ((PushedRegisterNumber)string).getValue().value()));
					}
					else if (string instanceof PushedString) {
						sb.append(String.format("                      %03d: \"%s\"", i, ((PushedString)string).getValue().value()));
					}
					else if (string instanceof PushedUndefined) {
						sb.append(String.format("                      %03d: undefined", i));
					}
					else if (string instanceof PushedUnknown) {
						sb.append(String.format("                      %03d: unknown", i));
					}

					if (i != strings.size() - 1) {
						sb.append('\n');
					}
				}
			}

			@Override
			protected void visit(final ActionPushDuplicate instruction) {
				add(sb, "PushDuplicate");
			}

			@Override
			protected void visit(final ActionRandomNumber instruction) {
				add(sb, "RandomNumber");
			}

			@Override
			protected void visit(final ActionRemoveSprite instruction) {
				add(sb, "RemoveSprite");
			}

			@Override
			protected void visit(final ActionReturn instruction) {
				add(sb, "Return");
			}

			@Override
			protected void visit(final ActionSetMember instruction) {
				add(sb, "SetMember");
			}

			@Override
			protected void visit(final ActionSetProperty instruction) {
				add(sb, "SetProperty");
			}

			@Override
			protected void visit(final ActionSetTarget instruction) {
				add(sb, "SetTarget", instruction.getTargetName().value());
			}

			@Override
			protected void visit(final ActionSetTarget2 instruction) {
				add(sb, "SetTarget2");
			}

			@Override
			protected void visit(final ActionSetVariable instruction) {
				add(sb, "SetVariable");
			}

			@Override
			protected void visit(final ActionStackSwap instruction) {
				add(sb, "StackSwap");
			}

			@Override
			protected void visit(final ActionStartDrag instruction) {
				add(sb, "StartDrag");
			}

			@Override
			protected void visit(final ActionStop instruction) {
				add(sb, "Stop");
			}

			@Override
			protected void visit(final ActionStopSounds instruction) {
				add(sb, "StopSounds");
			}

			@Override
			protected void visit(final ActionStoreRegister instruction) {
				add(sb, "StoreRegister", instruction.getRegisterNumber().value());
			}

			@Override
			protected void visit(final ActionStrictEquals instruction) {
				add(sb, "StrictEquals");
			}

			@Override
			protected void visit(final ActionStringAdd instruction) {
				add(sb, "StringAdd");
			}

			@Override
			protected void visit(final ActionStringEquals instruction) {
				add(sb, "StringEquals");
			}

			@Override
			protected void visit(final ActionStringExtract instruction) {
				add(sb, "StringExtract");
			}

			@Override
			protected void visit(final ActionStringGreater instruction) {
				add(sb, "StringGreater");
			}

			@Override
			protected void visit(final ActionStringLength instruction) {
				add(sb, "StringLength");
			}

			@Override
			protected void visit(final ActionStringLess instruction) {
				add(sb, "StringLess");
			}

			@Override
			protected void visit(final ActionSubtract instruction) {
				add(sb, "Subtract");
			}

			@Override
			protected void visit(final ActionTargetPath instruction) {
				add(sb, "TargetPath");
			}

			@Override
			protected void visit(final ActionThrow instruction) {
				add(sb, "Throw");
			}

			@Override
			protected void visit(final ActionToggleQuality instruction) {
				add(sb, "ToggleQuality");
			}

			@Override
			protected void visit(final ActionToInteger instruction) {
				add(sb, "ToInteger");
			}

			@Override
			protected void visit(final ActionToNumbers instruction) {
				add(sb, "ToNumbers");
			}

			@Override
			protected void visit(final ActionToString instruction) {
				add(sb, "ToString");
			}

			@Override
			protected void visit(final ActionTrace instruction) {
				add(sb, "Trace");
			}

			@Override
			protected void visit(final ActionTry instruction) {
				add(sb, "Try");
			}

			@Override
			protected void visit(final ActionTypeOf instruction) {
				add(sb, "TypeOf");
			}

			@Override
			protected void visit(final ActionUnknown instruction) {
				add(sb, String.format("Unknown %02X", instruction.getActionCode().value()));
			}

			@Override
			protected void visit(final ActionWaitForFrame instruction) {
				add(sb, "WaitForFrame", instruction.getFrame().value());
			}

			@Override
			protected void visit(final ActionWaitForFrame2 instruction) {
				add(sb, "WaitForFrame2", instruction.getSkipCount().value());
			}

			@Override
			protected void visit(final ActionWith instruction) {
				add(sb, "With");
				sb.append('\n');
				sb.append("{\n");
				sb.append(getCodeText(instruction.getActions()));
				sb.append("\n}");
			}
		}.visit(instruction);
	}

	/**
	 * Generates a printable string that represents all code in a given
	 * action list.
	 * 
	 * @param code The list that contains the ActionScript 2 code.
	 * 
	 * @return The generated ActionScript 2 code string.
	 */
	public static String getCodeText(final ActionList code) {
		return getCodeText(code, "");
	}

	/**
	 * Generates a printable string that represents all code in a given
	 * action list.
	 * 
	 * @param code The list that contains the ActionScript 2 code.
	 * @param padding Padding string that is prepended before every line.
	 * 
	 * @return The generated ActionScript 2 code string.
	 */
	public static String getCodeText(final ActionList code, final String padding) {
		if (code.size() == 0) {
			return "";
		}

		final StringBuilder sb = new StringBuilder();

		final Action firstInstruction = code.get(0);

		final int firstOffset = firstInstruction.getBitPosition();

		for (final Action instruction : code) {

			final int absoluteOffset = instruction.getBitPosition() / 8;
			final int relativeOffset = absoluteOffset - firstOffset  / 8;
			sb.append(String.format("%s%08X %08X  ", padding, absoluteOffset, relativeOffset));

			addInstructionText(sb, instruction, firstOffset);

			sb.append('\n');
		}

		return sb.toString();
	}
}