/*
 *  Copyright (C) 2010-2021 JPEXS
 * 
 *  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 3 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.
 * 
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.jpexs.decompiler.flash.gui;

import com.jpexs.decompiler.flash.SWF;
import com.jpexs.decompiler.flash.abc.ABC;
import com.jpexs.decompiler.flash.abc.avm2.AVM2Code;
import com.jpexs.decompiler.flash.abc.avm2.AVM2Runtime;
import com.jpexs.decompiler.flash.abc.avm2.AVM2RuntimeInfo;
import com.jpexs.decompiler.flash.abc.avm2.exceptions.AVM2ExecutionException;
import com.jpexs.decompiler.flash.abc.avm2.instructions.AVM2Instruction;
import com.jpexs.decompiler.flash.abc.avm2.instructions.AVM2Instructions;
import com.jpexs.decompiler.flash.abc.avm2.instructions.IfTypeIns;
import com.jpexs.decompiler.flash.abc.avm2.instructions.InstructionDefinition;
import com.jpexs.decompiler.flash.abc.types.MethodBody;
import com.jpexs.decompiler.flash.action.Action;
import com.jpexs.decompiler.flash.action.ActionLocalData;
import com.jpexs.decompiler.flash.action.LocalDataArea;
import com.jpexs.decompiler.flash.action.Stage;
import com.jpexs.decompiler.flash.action.swf4.ActionAdd;
import com.jpexs.decompiler.flash.action.swf4.ActionAnd;
import com.jpexs.decompiler.flash.action.swf4.ActionAsciiToChar;
import com.jpexs.decompiler.flash.action.swf4.ActionCharToAscii;
import com.jpexs.decompiler.flash.action.swf4.ActionDivide;
import com.jpexs.decompiler.flash.action.swf4.ActionEquals;
import com.jpexs.decompiler.flash.action.swf4.ActionLess;
import com.jpexs.decompiler.flash.action.swf4.ActionMBAsciiToChar;
import com.jpexs.decompiler.flash.action.swf4.ActionMBCharToAscii;
import com.jpexs.decompiler.flash.action.swf4.ActionMBStringExtract;
import com.jpexs.decompiler.flash.action.swf4.ActionMBStringLength;
import com.jpexs.decompiler.flash.action.swf4.ActionMultiply;
import com.jpexs.decompiler.flash.action.swf4.ActionNot;
import com.jpexs.decompiler.flash.action.swf4.ActionOr;
import com.jpexs.decompiler.flash.action.swf4.ActionPush;
import com.jpexs.decompiler.flash.action.swf4.ActionStringAdd;
import com.jpexs.decompiler.flash.action.swf4.ActionStringExtract;
import com.jpexs.decompiler.flash.action.swf4.ActionStringLength;
import com.jpexs.decompiler.flash.action.swf4.ActionStringLess;
import com.jpexs.decompiler.flash.action.swf4.ActionSubtract;
import com.jpexs.decompiler.flash.action.swf4.ActionToInteger;
import com.jpexs.decompiler.flash.action.swf5.ActionAdd2;
import com.jpexs.decompiler.flash.action.swf5.ActionBitAnd;
import com.jpexs.decompiler.flash.action.swf5.ActionBitLShift;
import com.jpexs.decompiler.flash.action.swf5.ActionBitOr;
import com.jpexs.decompiler.flash.action.swf5.ActionBitRShift;
import com.jpexs.decompiler.flash.action.swf5.ActionBitURShift;
import com.jpexs.decompiler.flash.action.swf5.ActionBitXor;
import com.jpexs.decompiler.flash.action.swf5.ActionDecrement;
import com.jpexs.decompiler.flash.action.swf5.ActionEquals2;
import com.jpexs.decompiler.flash.action.swf5.ActionIncrement;
import com.jpexs.decompiler.flash.action.swf5.ActionLess2;
import com.jpexs.decompiler.flash.action.swf5.ActionModulo;
import com.jpexs.decompiler.flash.action.swf5.ActionPushDuplicate;
import com.jpexs.decompiler.flash.action.swf5.ActionStackSwap;
import com.jpexs.decompiler.flash.action.swf5.ActionToNumber;
import com.jpexs.decompiler.flash.action.swf5.ActionToString;
import com.jpexs.decompiler.flash.action.swf5.ActionTypeOf;
import com.jpexs.decompiler.flash.action.swf6.ActionGreater;
import com.jpexs.decompiler.flash.action.swf6.ActionStrictEquals;
import com.jpexs.decompiler.flash.action.swf6.ActionStringGreater;
import com.jpexs.decompiler.flash.ecma.EcmaScript;
import com.jpexs.decompiler.flash.ecma.Null;
import com.jpexs.decompiler.flash.ecma.Undefined;
import com.jpexs.decompiler.flash.tags.ABCContainerTag;
import com.jpexs.decompiler.graph.Graph;
import com.jpexs.decompiler.graph.GraphTargetItem;
import com.jpexs.decompiler.graph.TranslateStack;
import com.jpexs.helpers.Helper;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Random;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;

/**
 *
 * @author JPEXS
 */
public class FlashPlayerTest {

    private final Random random = new Random();

    private final AVM2RuntimeInfo adobeRuntime = new AVM2RuntimeInfo(AVM2Runtime.ADOBE_FLASH, 19, true);

    //@Test
    public void testAs3Files() throws IOException, InterruptedException {
        List<String> files = new ArrayList<>();
        File dir = new File("..\\swf");
        if (dir.exists()) {
            File[] fs = dir.listFiles(new FilenameFilter() {
                @Override
                public boolean accept(File dir, String name) {
                    return name.toLowerCase().endsWith(".swf") && !name.toLowerCase().endsWith(".recompiled.swf");
                }
            });
            for (File f : fs) {
                files.add(dir.getAbsolutePath() + File.separator + f.getName());
            }
        }

        for (String file : files) {
            SWF swf = new SWF(new BufferedInputStream(new FileInputStream(file)), true);
            List<ABCContainerTag> abcs = swf.getAbcList();
            for (ABCContainerTag abcContainer : abcs) {
                ABC abc = abcContainer.getABC();

                for (MethodBody body : abc.bodies) {
                    if (body.method_info != 55) {
                        continue;
                    }

                    AVM2Code code = body.getCode();

                    AdobeFlashExecutor adobeExecutor = new AdobeFlashExecutor();

                    adobeExecutor.loadTestSwf();
                    ABC testAbc = adobeExecutor.as3TestSwfAbcTag.getABC();
                    testAbc.constants.ensureStringCapacity(1000000);
                    testAbc.constants.ensureMultinameCapacity(1000000);

                    List<AS3ExecuteTask> tasks = new ArrayList<>();
                    {
                        AS3ExecuteTask task = new AS3ExecuteTask();
                        task.description = swf.getFileTitle() + " " + body.method_info;
                        while (code.code.size() > 9) {
                            code.code.remove(code.code.size() - 1);
                        }
                        task.code = code;
                        tasks.add(task);
                    }

                    adobeExecutor.executeAvm2(tasks);

                    for (AS3ExecuteTask task : tasks) {

                        String ffdecExecuteResult;
                        try {
                            Object res = task.code.execute(new HashMap<>(), testAbc.constants, adobeRuntime);
                            ffdecExecuteResult = "Result:" + EcmaScript.toString(res) + " Type:" + EcmaScript.typeString(res);
                        } catch (AVM2ExecutionException ex) {
                            ffdecExecuteResult = "Error:" + ex.getMessage();
                        }

                        task.ffdecResult = ffdecExecuteResult;
                    }

                    for (AS3ExecuteTask task : tasks) {
                        System.out.println("Flash result (" + task.description + "): " + task.flashResult);
                        System.out.println("FFDec execte result: " + task.ffdecResult);
                        if (!task.ffdecResult.equals(task.flashResult)) {
                            System.out.println(code.toASMSource(testAbc, testAbc.constants));
                        }

                        assertEquals(task.ffdecResult, task.flashResult);
                    }
                }
            }
        }
    }

    //@Test
    public void testAs3Pushes() throws IOException, InterruptedException {
        AdobeFlashExecutor adobeExecutor = new AdobeFlashExecutor();

        adobeExecutor.loadTestSwf();
        ABC abc = adobeExecutor.as3TestSwfAbcTag.getABC();
        abc.constants.ensureStringCapacity(1000000);
        abc.constants.ensureMultinameCapacity(1000000);
        AVM2Instruction[] pushes = getAs3Pushes(abc);

        List<AS3ExecuteTask> tasks = new ArrayList<>();
        for (int p1 = 0; p1 < pushes.length; p1++) {
            AVM2Code ccode = new AVM2Code();
            List<AVM2Instruction> code = ccode.code;
            code.add(new AVM2Instruction(0, AVM2Instructions.GetLocal0, null));
            code.add(new AVM2Instruction(0, AVM2Instructions.PushScope, null));
            code.add(pushes[p1].clone());
            code.add(new AVM2Instruction(0, AVM2Instructions.ReturnValue, null));
            ccode.markOffsets();

            AS3ExecuteTask task = new AS3ExecuteTask();
            task.description = p1 + ", " + pushes[p1].definition.instructionName;
            task.code = ccode;
            tasks.add(task);
        }

        adobeExecutor.executeAvm2(tasks);

        for (AS3ExecuteTask task : tasks) {
            String ffdecExecuteResult;
            try {
                Object res = task.code.execute(new HashMap<>(), abc.constants, adobeRuntime);
                ffdecExecuteResult = "Result:" + EcmaScript.toString(res) + " Type:" + EcmaScript.typeString(res);
            } catch (AVM2ExecutionException ex) {
                ffdecExecuteResult = "Error:" + ex.getMessage();
            }

            task.ffdecResult = ffdecExecuteResult;
        }

        for (AS3ExecuteTask task : tasks) {
            System.out.println("Flash result (" + task.description + "): " + task.flashResult);
            System.out.println("FFDec execte result: " + task.ffdecResult);
            assertEquals(task.ffdecResult, task.flashResult);
        }
    }

    //@Test
    public void testAs3() throws IOException, InterruptedException {
        AdobeFlashExecutor adobeExecutor = new AdobeFlashExecutor();

        for (int i = 0; i < 256; i++) {
            System.out.println("Instruction code: " + Integer.toHexString(i) + " (" + i + ")");
            List<AS3ExecuteTask> tasks = new ArrayList<>();
            adobeExecutor.loadTestSwf();
            ABC abc = adobeExecutor.as3TestSwfAbcTag.getABC();
            AVM2Instruction[] pushes = getAs3Pushes(abc);
            for (int p1 = 0; p1 < pushes.length; p1++) {
                for (int p2 = 0; p2 < pushes.length; p2++) {
                    // todo: the following instructions are not implemented
                    if (i == AVM2Instructions.GetSuper
                            || i == AVM2Instructions.SetSuper
                            || i == AVM2Instructions.DXNS
                            || i == AVM2Instructions.DXNSLate
                            || i == AVM2Instructions.Kill
                            || i == AVM2Instructions.LookupSwitch
                            || i == AVM2Instructions.PushWith
                            || i == AVM2Instructions.NextName
                            || i == AVM2Instructions.HasNext
                            || i == AVM2Instructions.NextValue
                            || i == AVM2Instructions.PushScope
                            || i == AVM2Instructions.PushNamespace
                            || i == AVM2Instructions.HasNext2
                            || i == AVM2Instructions.NewFunction
                            || i == AVM2Instructions.Call
                            || i == AVM2Instructions.Construct
                            || i == AVM2Instructions.CallMethod
                            || i == AVM2Instructions.CallStatic
                            || i == AVM2Instructions.CallSuper
                            || i == AVM2Instructions.CallProperty
                            || i == AVM2Instructions.ConstructSuper
                            || i == AVM2Instructions.ConstructProp
                            || i == AVM2Instructions.CallPropLex
                            || i == AVM2Instructions.CallSuperVoid
                            || i == AVM2Instructions.CallPropVoid
                            || i == AVM2Instructions.ApplyType
                            || i == AVM2Instructions.NewObject
                            || i == AVM2Instructions.NewArray
                            || i == AVM2Instructions.NewActivation
                            || i == AVM2Instructions.NewClass
                            || i == AVM2Instructions.GetDescendants
                            || i == AVM2Instructions.NewCatch
                            || i == AVM2Instructions.FindPropGlobal
                            || i == AVM2Instructions.FindPropertyStrict
                            || i == AVM2Instructions.FindProperty
                            || i == AVM2Instructions.FindDef
                            || i == AVM2Instructions.GetLex
                            || i == AVM2Instructions.SetProperty
                            || i == AVM2Instructions.GetGlobalScope
                            || i == AVM2Instructions.GetScopeObject
                            || i == AVM2Instructions.GetProperty
                            || i == AVM2Instructions.GetOuterScope
                            || i == AVM2Instructions.InitProperty
                            || i == AVM2Instructions.DeleteProperty
                            || i == AVM2Instructions.GetSlot
                            || i == AVM2Instructions.SetSlot
                            || i == AVM2Instructions.GetGlobalSlot
                            || i == AVM2Instructions.SetGlobalSlot
                            || i == AVM2Instructions.CheckFilter
                            || i == AVM2Instructions.AsType // todo: fix
                            || i == AVM2Instructions.AsTypeLate
                            || i == AVM2Instructions.InstanceOf
                            || i == AVM2Instructions.IsType
                            || i == AVM2Instructions.IsTypeLate
                            || i == AVM2Instructions.In) {
                        continue;
                    }

                    AVM2Code ccode = new AVM2Code();
                    List<AVM2Instruction> code = ccode.code;
                    code.add(new AVM2Instruction(0, AVM2Instructions.GetLocal0, null));
                    code.add(new AVM2Instruction(0, AVM2Instructions.PushScope, null));
                    code.add(new AVM2Instruction(0, AVM2Instructions.PushByte, new int[]{10}));
                    code.add(new AVM2Instruction(0, AVM2Instructions.SetLocal0, null));
                    code.add(new AVM2Instruction(0, AVM2Instructions.PushByte, new int[]{11}));
                    code.add(new AVM2Instruction(0, AVM2Instructions.SetLocal1, null));
                    code.add(new AVM2Instruction(0, AVM2Instructions.PushByte, new int[]{0}));
                    code.add(pushes[p1].clone());
                    code.add(pushes[p2].clone());

                    InstructionDefinition ins = AVM2Code.instructionSet[i];
                    int[] params = null;
                    boolean ifType = false;
                    if (ins.operands.length > 0) {
                        params = new int[ins.operands.length];
                        if (!(ins instanceof IfTypeIns)) {
                            for (int param = 0; param < params.length; param++) {
                                params[param] = 1;
                            }
                        }

                        ifType = ins instanceof IfTypeIns;
                        if (ifType) {
                            params[0] = 3;
                        }
                    }

                    code.add(new AVM2Instruction(0, ins, params));
                    if (ifType) {
                        code.add(new AVM2Instruction(0, AVM2Instructions.PushString, new int[]{1}));
                        code.add(new AVM2Instruction(0, AVM2Instructions.ReturnValue, null));
                    }

                    code.add(new AVM2Instruction(0, AVM2Instructions.Dup, null));
                    code.add(new AVM2Instruction(0, AVM2Instructions.TypeOf, null));
                    code.add(new AVM2Instruction(0, AVM2Instructions.Add, null));
                    code.add(new AVM2Instruction(0, AVM2Instructions.ReturnValue, null));
                    ccode.markOffsets();

                    AS3ExecuteTask task = new AS3ExecuteTask();
                    task.description = "Instruction code: " + Integer.toHexString(i) + " (" + i + ") " + AVM2Code.instructionSet[i].instructionName + ", "
                            + "p1: " + p1 + ", " + pushes[p1].definition.instructionName + ", "
                            + "p2: " + p2 + ", " + pushes[p2].definition.instructionName;
                    task.code = ccode;

                    String ffdecExecuteResult;
                    try {
                        Object res = ccode.execute(new HashMap<>(), abc.constants, adobeRuntime);
                        ffdecExecuteResult = "Result:" + EcmaScript.toString(res) + " Type:" + EcmaScript.typeString(res);
                    } catch (AVM2ExecutionException ex) {
                        ffdecExecuteResult = "Error:" + ex.getMessage();
                    }

                    task.ffdecResult = ffdecExecuteResult;
                    tasks.add(task);
                }
            }

            adobeExecutor.executeAvm2(tasks);

            StringBuilder expeced = new StringBuilder();
            StringBuilder current = new StringBuilder();
            for (AS3ExecuteTask task : tasks) {
                if (!task.flashResult.equals(task.ffdecResult)) {
                    System.out.println("Flash result (" + task.description + "): " + task.flashResult);
                    System.out.println("FFDec execte result: " + task.ffdecResult);
                    expeced.append(task.flashResult).append(Helper.newLine);
                    current.append(task.ffdecResult).append(Helper.newLine);
                }

                /*if (!task.ffdecResult.equals(task.flashResult)) {
                 String ffdecExecuteResult;
                 try {
                 Object res = task.code.execute(new HashMap<>(), abc.constants, adobeRuntime);
                 ffdecExecuteResult = "Result:" + EcmaScript.toString(res) + " Type:" + EcmaScript.typeString(res);
                 } catch (AVM2ExecutionException ex) {
                 ffdecExecuteResult = "Error:" + ex.getMessage();
                 }
                 }*/
                assertEquals(task.ffdecResult, task.flashResult);
            }

            //Helper.writeFile("expected\\" + i + ".txt", Utf8Helper.getBytes(expeced.toString()));
            //Helper.writeFile("current\\" + i + ".txt", Utf8Helper.getBytes(current.toString()));
        }
    }

    private AVM2Instruction[] getAs3Pushes(ABC abc) {
        AVM2Instruction[] pushes = new AVM2Instruction[]{
            new AVM2Instruction(0, AVM2Instructions.PushUndefined, null), // 0
            new AVM2Instruction(0, AVM2Instructions.PushNull, null), // 1
            new AVM2Instruction(0, AVM2Instructions.PushTrue, null), // 2
            new AVM2Instruction(0, AVM2Instructions.PushFalse, null), // 3
            new AVM2Instruction(0, AVM2Instructions.PushNan, null), // 4
            new AVM2Instruction(0, AVM2Instructions.PushString, new int[]{0}), // 5
            new AVM2Instruction(0, AVM2Instructions.PushString, new int[]{abc.constants.getStringId("", true)}), // 6
            new AVM2Instruction(0, AVM2Instructions.PushString, new int[]{abc.constants.getStringId("-2147483649", true)}), // 7
            new AVM2Instruction(0, AVM2Instructions.PushString, new int[]{abc.constants.getStringId("-2147483648", true)}), // 8
            new AVM2Instruction(0, AVM2Instructions.PushString, new int[]{abc.constants.getStringId("-2147483647", true)}), // 9
            new AVM2Instruction(0, AVM2Instructions.PushString, new int[]{abc.constants.getStringId("-1", true)}), // 10
            new AVM2Instruction(0, AVM2Instructions.PushString, new int[]{abc.constants.getStringId("0", true)}), // 11
            new AVM2Instruction(0, AVM2Instructions.PushString, new int[]{abc.constants.getStringId("1", true)}), // 12
            new AVM2Instruction(0, AVM2Instructions.PushString, new int[]{abc.constants.getStringId("2147483647", true)}), // 13
            new AVM2Instruction(0, AVM2Instructions.PushString, new int[]{abc.constants.getStringId("2147483648", true)}), // 14
            new AVM2Instruction(0, AVM2Instructions.PushString, new int[]{abc.constants.getStringId("4294967295", true)}), // 15
            new AVM2Instruction(0, AVM2Instructions.PushString, new int[]{abc.constants.getStringId("4294967296", true)}), // 16
            new AVM2Instruction(0, AVM2Instructions.PushString, new int[]{abc.constants.getStringId("1test", true)}), // 17
            new AVM2Instruction(0, AVM2Instructions.PushString, new int[]{abc.constants.getStringId("test", true)}), // 18
            new AVM2Instruction(0, AVM2Instructions.PushString, new int[]{abc.constants.getStringId("test2", true)}), // 18
            new AVM2Instruction(0, AVM2Instructions.PushString, new int[]{abc.constants.getStringId("test3", true)}), // 18
            new AVM2Instruction(0, AVM2Instructions.PushDouble, new int[]{0}), // 19
            new AVM2Instruction(0, AVM2Instructions.PushDouble, new int[]{abc.constants.getDoubleId(-1, true)}), // 20
            new AVM2Instruction(0, AVM2Instructions.PushDouble, new int[]{abc.constants.getDoubleId(-0.5, true)}), // 21
            new AVM2Instruction(0, AVM2Instructions.PushDouble, new int[]{abc.constants.getDoubleId(0, true)}), // 22
            new AVM2Instruction(0, AVM2Instructions.PushDouble, new int[]{abc.constants.getDoubleId(0.5, true)}), // 23
            new AVM2Instruction(0, AVM2Instructions.PushDouble, new int[]{abc.constants.getDoubleId(1, true)}), // 24
            new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{-2147483648}), // 25
            new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{-2147483647}), // 26
            new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{-1073741824}), // 27
            new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{-1073741823}), // 28
            new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{-536870912}), // 29
            new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{-536870911}), // 30
            new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{-268435456}), // 31
            new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{-134217728}), // 32
            new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{-134217727}), // 33
            new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{-67108864}), // 34
            new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{-67108863}), // 35
            new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{-33554432}), // 36
            new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{-33554431}), // 37
            new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{-16777216}), // 38
            new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{-16777215}), // 39
            new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{-8388608}), // 40
            new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{-8388607}), // 41
            new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{-4194304}), // 42
            new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{-4194303}), // 43
            new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{-2097152}), // 44
            new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{-2097151}), // 45
            new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{-1048576}), // 46
            new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{-1048575}), // 47
            new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{-524288}), // 48
            new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{-524287}), // 49
            new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{-262144}), // 50
            new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{-262143}), // 51
            new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{-131072}), // 52
            new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{-131071}), // 53
            new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{-65536}), // 54
            new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{-65535}), // 55
            new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{-32768}), // 56
            new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{-32767}), // 57
            new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{-1}), // 58
            new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{0}), // 59
            new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{1}), // 60
            /*new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{127}), // 61
             new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{128}), // 62
             new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{255}), // 61
             new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{256}), // 62
             new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{511}), // 61
             new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{512}), // 62
             new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{1023}), // 61
             new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{1024}), // 62
             new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{2047}), // 61
             new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{2048}), // 62
             new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{4095}), // 61
             new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{4096}), // 62
             new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{8191}), // 61
             new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{8192}), // 62
             new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{16383}), // 61
             new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{16384}), // 62*/
            new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{32767}), // 61
            new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{32768}), // 62
            new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{268435455}), // 63
            new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{268435456}), // 64
            new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{536870911}), // 65
            new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{536870912}), // 66
            new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{1073741823}), // 67
            new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{1073741824}), // 68
            new AVM2Instruction(0, AVM2Instructions.PushShort, new int[]{2147483647}), // 69
            new AVM2Instruction(0, AVM2Instructions.PushInt, new int[]{0}), // 70
            new AVM2Instruction(0, AVM2Instructions.PushInt, new int[]{abc.constants.getIntId(-2147483649L, true)}), // 71
            new AVM2Instruction(0, AVM2Instructions.PushInt, new int[]{abc.constants.getIntId(-2147483648, true)}), // 72
            new AVM2Instruction(0, AVM2Instructions.PushInt, new int[]{abc.constants.getIntId(-1, true)}), // 73
            new AVM2Instruction(0, AVM2Instructions.PushInt, new int[]{abc.constants.getIntId(0, true)}), // 74
            new AVM2Instruction(0, AVM2Instructions.PushInt, new int[]{abc.constants.getIntId(1, true)}), // 75
            new AVM2Instruction(0, AVM2Instructions.PushInt, new int[]{abc.constants.getIntId(2147483647, true)}), // 76
            new AVM2Instruction(0, AVM2Instructions.PushInt, new int[]{abc.constants.getIntId(2147483648L, true)}), // 77
            new AVM2Instruction(0, AVM2Instructions.PushUInt, new int[]{abc.constants.getIntId(4294967295L, true)}), // 78
            new AVM2Instruction(0, AVM2Instructions.PushUInt, new int[]{abc.constants.getIntId(4294967296L, true)}), // 79
            new AVM2Instruction(0, AVM2Instructions.PushUInt, new int[]{0}), // 80
            new AVM2Instruction(0, AVM2Instructions.PushUInt, new int[]{abc.constants.getUIntId(-2147483649L, true)}), // 81
            new AVM2Instruction(0, AVM2Instructions.PushUInt, new int[]{abc.constants.getUIntId(-2147483648, true)}), // 82
            new AVM2Instruction(0, AVM2Instructions.PushUInt, new int[]{abc.constants.getUIntId(-1, true)}), // 83
            new AVM2Instruction(0, AVM2Instructions.PushUInt, new int[]{abc.constants.getUIntId(0, true)}), // 84
            new AVM2Instruction(0, AVM2Instructions.PushUInt, new int[]{abc.constants.getUIntId(1, true)}), // 85
            new AVM2Instruction(0, AVM2Instructions.PushUInt, new int[]{abc.constants.getUIntId(2147483647, true)}), // 86
            new AVM2Instruction(0, AVM2Instructions.PushUInt, new int[]{abc.constants.getUIntId(2147483648L, true)}), // 87
            new AVM2Instruction(0, AVM2Instructions.PushUInt, new int[]{abc.constants.getUIntId(4294967295L, true)}), // 88
            new AVM2Instruction(0, AVM2Instructions.PushUInt, new int[]{abc.constants.getUIntId(4294967296L, true)}), // 89
            new AVM2Instruction(0, AVM2Instructions.PushByte, new int[]{-2147483648}), // 90
            new AVM2Instruction(0, AVM2Instructions.PushByte, new int[]{-256}), // 91
            new AVM2Instruction(0, AVM2Instructions.PushByte, new int[]{-255}), // 92
            new AVM2Instruction(0, AVM2Instructions.PushByte, new int[]{-128}), // 93
            new AVM2Instruction(0, AVM2Instructions.PushByte, new int[]{-127}), // 94
            new AVM2Instruction(0, AVM2Instructions.PushByte, new int[]{-2}), // 95
            new AVM2Instruction(0, AVM2Instructions.PushByte, new int[]{-1}), // 96
            new AVM2Instruction(0, AVM2Instructions.PushByte, new int[]{0}), // 97
            new AVM2Instruction(0, AVM2Instructions.PushByte, new int[]{1}), // 98
            new AVM2Instruction(0, AVM2Instructions.PushByte, new int[]{2}), // 99
            new AVM2Instruction(0, AVM2Instructions.PushByte, new int[]{127}), // 100
            new AVM2Instruction(0, AVM2Instructions.PushByte, new int[]{128}), // 101
            new AVM2Instruction(0, AVM2Instructions.PushByte, new int[]{255}), // 102
            new AVM2Instruction(0, AVM2Instructions.PushByte, new int[]{256}), // 103
            new AVM2Instruction(0, AVM2Instructions.PushByte, new int[]{2147483647}),}; // 104
        return pushes;
    }

    //@Test
    public void testAs2() throws InterruptedException {
        AdobeFlashExecutor adobeExecutor = new AdobeFlashExecutor();

        List<AS2ExecuteTask> tasks = new ArrayList<>();
        Object[] pushes = getAs2Pushes();
        for (int i = 0; i < 13 + 23 + 2; i++) {
            for (int p1 = 0; p1 < 15; p1++) {
                int p2Count = 1;
                if (i >= 13) {
                    p2Count = pushes.length;
                }

                for (int p2 = 0; p2 < p2Count; p2++) {

                    List<Action> newActions = new ArrayList<>();
                    Action opAction = getOpAction(i);

                    if (i >= 13 + 23) {
                        newActions.add(new ActionPush("mystring_\u00E1rv\u00EDzt\u0171r\u0151_t\u00FCk\u00F6rf\u00FAr\u00F3g\u00E9p"));
                    }

                    Object p1o = pushes[p1];
                    Object p2o = null;
                    if (i >= 13) {
                        p2o = pushes[p2];
                        newActions.add(new ActionPush(p2o));
                    }

                    newActions.add(new ActionPush(p1o));

                    newActions.add(opAction);
                    newActions.add(new ActionPushDuplicate());
                    newActions.add(new ActionTypeOf());
                    newActions.add(new ActionStackSwap());
                    newActions.add(new ActionStringAdd());

                    AS2ExecuteTask task = new AS2ExecuteTask();
                    String desc = i + " " + opAction.toString() + " p1:"
                            + (p1o instanceof String ? "'" + p1o + "'" : p1o) + " p2:"
                            + (p2o instanceof String ? "'" + p2o + "'" : p2o) + " r3:" + "mystring";
                    task.description = desc;
                    task.actions = newActions;

                    List<GraphTargetItem> output = new ArrayList<>();
                    ActionLocalData localData = new ActionLocalData(null, false);
                    TranslateStack stack = new TranslateStack("");
                    for (Action a : newActions) {
                        a.translate(localData, stack, output, Graph.SOP_USE_STATIC, "");
                    }

                    String ffdecTranslateResult;
                    try {
                        Object res = stack.pop().getResult();
                        ffdecTranslateResult = "Result:" + EcmaScript.toString(res) + " Type:" + EcmaScript.typeString(res);
                    } catch (Exception e) {
                        ffdecTranslateResult = "Error:" + e.getMessage();
                    }

                    String ffdecExecuteResult;
                    try {
                        LocalDataArea lda = new LocalDataArea(new Stage(null));
                        for (Action a : newActions) {
                            if (!a.execute(lda)) {
                                fail();
                            }
                        }

                        Object res = lda.pop();
                        ffdecExecuteResult = "Result:" + EcmaScript.toString(res) + " Type:" + EcmaScript.typeString(res);
                    } catch (Exception e) {
                        ffdecExecuteResult = "Error:" + e.getMessage();
                    }

                    assertEquals(ffdecTranslateResult, ffdecExecuteResult);

                    task.ffdecResult = ffdecExecuteResult;
                    tasks.add(task);
                }
            }
        }

        adobeExecutor.executeActionLists(tasks);

        StringBuilder expeced = new StringBuilder();
        StringBuilder current = new StringBuilder();
        for (AS2ExecuteTask task : tasks) {
            if (!task.flashResult.equals(task.ffdecResult)) {
                System.out.println("Flash result (" + task.description + "): " + task.flashResult);
                System.out.println("FFDec result: " + task.ffdecResult);
                expeced.append(task.description).append(task.flashResult).append(Helper.newLine);
                current.append(task.description).append(task.ffdecResult).append(Helper.newLine);
            }

            boolean checkOnlyStart = false;
            /*if (flashResult.length() > 10) {
             boolean onlyNumber = true;
             for (int k = 0; k < 10; k++) {
             char ch = flashResult.charAt(flashResult.length() - k - 1);
             if (ch < '0' || ch > '9') {
             onlyNumber = false;
             break;
             }
             }

             if (onlyNumber) {
             flashResult = flashResult.substring(0, flashResult.length() - 1);
             checkOnlyStart = true;
             }
             }*/

 /*if (!ffdecResult.equals(flashResult)) {
             LocalDataArea lda = new LocalDataArea();
             for (Action a : task.actions) {
             if (!a.execute(lda)) {
             fail();
             }
             }

             Object res = lda.stack.pop();
             }*/
            if (checkOnlyStart) {
                assertTrue(((String) task.ffdecResult).startsWith(task.flashResult));
            } else {
                assertEquals(task.ffdecResult, task.flashResult);
            }
        }

        //Helper.writeFile("expected.txt", Utf8Helper.getBytes(expeced.toString()));
        //Helper.writeFile("current.txt", Utf8Helper.getBytes(current.toString()));
    }

    private Object[] getAs2Pushes() {
        int r1 = random.nextInt(500) - 255;
        int r2 = random.nextInt(100);
        Object[] pushes = new Object[]{
            Undefined.INSTANCE, Null.INSTANCE,
            false, true,
            Double.NaN,
            "", "-2147483649", "-2147483648", "-2147483647", "-1", "0", "1", "2147483647", "2147483648", "4294967295", "4294967296", "1test", "test", "test2", "test3", "0.0", "1.0", "-1.0",
            -1.0, -0.5, 0, 0.5, 1.0,
            -2147483648, -2147483647, -1073741824, -1073741823, -536870912, -536870911, -268435456, -134217728, -134217727, -67108864, -67108863, -33554432,
            -33554431, -16777216, -16777215, -8388608, -8388607, -4194304, -4194303, -2097152, -2097151, -1048576, -1048575, -524288, -524287, -262144, -262143, -131072, -131071, -65536, -65535, -32768, -32767, -1, 0, 1, 32767, 32768, 268435455,
            -100, 100,
            //r1, r2
            -225, 66
        };
        return pushes;
    }

    private Action getOpAction(int idx) {
        Action result;
        if (idx < 13) {
            result = getUnaryOpAction(idx);
            assertEquals(1, result.getStackPopCount(null, null));
            assertEquals(1, result.getStackPushCount(null, null));
        } else if (idx < 13 + 23) {
            result = getBinaryOpAction(idx - 13);
            assertEquals(2, result.getStackPopCount(null, null));
            assertEquals(1, result.getStackPushCount(null, null));
        } else {
            result = getTernaryOpAction(idx - 13 - 23);
            assertEquals(3, result.getStackPopCount(null, null));
            assertEquals(1, result.getStackPushCount(null, null));
        }

        return result;
    }

    private Action getUnaryOpAction(int idx) {
        switch (idx) {
            case 0:
                return new ActionAsciiToChar();
            case 1:
                return new ActionCharToAscii();
            case 2:
                return new ActionDecrement();
            case 3:
                return new ActionIncrement();
            case 4:
                return new ActionNot();
            case 5:
                return new ActionToInteger();
            case 6:
                return new ActionToNumber();
            case 7:
                return new ActionToString();
            case 8:
                return new ActionTypeOf();
            case 9:
                return new ActionStringLength();
            case 10:
                return new ActionMBAsciiToChar();
            case 11:
                return new ActionMBCharToAscii();
            case 12:
                return new ActionMBStringLength();
        }

        throw new Error("Invalid index");
    }

    private Action getBinaryOpAction(int idx) {
        switch (idx) {
            case 0:
                return new ActionAnd();
            case 1:
                return new ActionAdd();
            case 2:
                return new ActionAdd2();
            case 3:
                return new ActionBitAnd();
            case 4:
                return new ActionBitLShift();
            case 5:
                return new ActionBitOr();
            case 6:
                return new ActionBitRShift();
            case 7:
                return new ActionBitURShift();
            case 8:
                return new ActionBitXor();
            case 9:
                return new ActionDivide();
            case 10:
                return new ActionEquals();
            case 11:
                return new ActionEquals2();
            case 12:
                return new ActionGreater();
            case 13:
                return new ActionLess();
            case 14:
                return new ActionLess2();
            case 15:
                return new ActionModulo();
            case 16:
                return new ActionMultiply();
            case 17:
                return new ActionOr();
            case 18:
                return new ActionStringAdd();
            case 19:
                return new ActionStrictEquals();
            case 20:
                return new ActionStringGreater();
            case 21:
                return new ActionStringLess();
            case 22:
                return new ActionSubtract();
        }

        throw new Error("Invalid index");
    }

    private Action getTernaryOpAction(int idx) {
        switch (idx) {
            case 0:
                return new ActionStringExtract();
            case 1:
                return new ActionMBStringExtract();
        }

        throw new Error("Invalid index");
    }
}
