The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package IO::SWF::Type::Action;

use strict;
use warnings;

use base 'IO::SWF::Type';

use IO::SWF::Bit;
use IO::SWF::Type::String;
use IO::SWF::Type::Float;
use IO::SWF::Type::Double;

our %action_code_table = (
    #
    # Opecode only
    # 
    0x04 => 'NextFrame',
    0x05 => 'PreviousFrame',
    0x06 => 'Play',
    0x07 => 'Stop',
    0x08 => 'ToggleQuality',
    0x09 => 'StopSounds',
    #
    0x0A => 'Add',
    0x0B => 'Substract',
    0x0C => 'Multiply',
    0x0D => 'Divide',
    0x0E => 'Equals',
    0x0F => 'Less',
    0x10 => 'And',
    0x11 => 'Or',
    0x12 => 'Not',
    0x13 => 'StringEquals',
    0x14 => 'StringLength',
    0x15 => 'StringExtract',
    #
    0x17 => 'Pop',
    0x18 => 'ToInteger',
    #
    0x1C => 'GetVariable',
    0x1D => 'SetVariable',
    #
    0x20 => 'SetTarget2',
    0x21 => 'StringAdd',
    0x22 => 'GetProperty',
    0x23 => 'SetProperty',
    0x24 => 'CloneSprite',
    0x25 => 'RemoveSprite',
    0x26 => 'Trace',
    0x2D => 'FSCommand2', # Flash Lite
    #

    #
    0x27 => 'StartDrag',
    0x28 => 'EndDrag',
    0x29 => 'StringLess',
    #
    0x30 => 'RandomNumber',
    0x31 => 'MBStringLength',
    0x32 => 'CharToAscii',
    0x33 => 'AsciiToChar',
    0x34 => 'GetTime',
    0x35 => 'MBStringExtract',
    0x36 => 'MBCharToAscii',
    0x37 => 'MBAsciiToChar',

    #
    0x3A => 'Delete', # SWF 5
    0x3B => 'Delete2', # SWF 5
    0x3C => 'DefineLocal', # SWF 5
    0x3D => 'CallFunction', # SWF 5
    0x3E => 'Return', # SWF 5
    0x3F => 'Modulo', # SWF 5
    0x40 => 'NewObject', # SWF 5
    0x41 => 'DefineLocal2', # SWF 5
    0x42 => 'InitArray', # SWF 5
    0x43 => 'InitObject', # SWF 5
    0x44 => 'TypeOf', # SWF 5
    0x45 => 'TargetPath', # SWF 5
    0x46 => 'Enumerate', # SWF 5
    0x47 => 'Add2', # SWF 5
    0x48 => 'Less2', # SWF 5
    0x49 => 'Equals2', # SWF 5
    0x4A => 'ToNumber', # SWF 5
    0x4B => 'ToString', # SWF 5
    0x4C => 'PushDuplicate', # SWF 5
    0x4D => 'StackSwap', # SWF 5
    0x4E => 'GetMember', # SWF 5
    0x4F => 'SetMember', # SWF 5
    0x50 => 'Increment', # SWF 5
    0x51 => 'Decrement', # SWF 5
    0x52 => 'CallMethod', # SWF 5
    0x53 => 'NewMethod', # SWF 5
    0x54 => 'InstanceOf', # SWF 6
    0x55 => 'Enumerate2', # SWF 6
    #
    0x60 => 'BitAnd', # SWF 5
    0x61 => 'BitOr', # SWF 5
    0x62 => 'BitXOr', # SWF 5
    0x63 => 'BitShift', # SWF 5
    0x64 => 'BitURShift', # SWF 5
    #
    0x66 => 'StrictEquals', # SWF 6
    0x67 => 'Greater', # SWF 6
    0x68 => 'StringGreater', # SWF 6

    #
    # has Data Payload
    0x81 => 'GotoFrame',
    0x83 => 'GetURL',
    0x87 => 'StoreRegister', # SWF 5
    0x88 => 'ConstantPool', # SWF 5
    0x8A => 'WaitForFrame',
    0x8B => 'SetTarget',
    0x8C => 'GoToLabel',
    0x8D => 'WaitForFrame2',

    #
    0x94 => 'With', # SWF 5
    0x96 => 'Push',
    #
    0x99 => 'Jump',
    0x9A => 'GetURL2',
    0x9B => 'DefineFunction', # SWF 5
    #
    0x9D => 'If',
    0x9E => 'Call', # why it >=0x80 ?
    0x9E => 'GotoFrame2',
);

sub getCodeName {
    my $code = shift;
    if (defined $action_code_table{$code}) {
        return $action_code_table{$code};
    } else {
        return "Unknown";
    }
}

sub parse {
    my ($reader, $opts_href) = @_;
    my (%action, $data, @strs);
    my $code = $reader->getUI8();
    $action{'Code'} = $code;
    if ($code >= 0x80) {
        my $length = $reader->getUI16LE();
        $action{'Length'} = $length;
        if ($code == 0x81) {
            # 0x81: // ActionGotoFrame
            $action{'Frame'} = $reader->getUI16LE();
        }
        elsif ($code == 0x83) {
            # 0x83: // ActionGetURL
            $data = $reader->getData($length);
            @strs = split("\0", $data, 2+1);
            $action{'UrlString'} = $strs[0];
            $action{'TargetString'} = $strs[1];
        }
        elsif ($code == 0x88) {
            # 0x88: // ActionConstantPool
            my $count = $reader->getUI16LE();
            $action{'Count'} = $count;
            $data = $reader->getData($length - 2);
            @strs = split("\0", $data, $count+1);
            my @ConstantPool = @strs[0 .. $count-1];
            $action{'ConstantPool'} = \@ConstantPool;
        }
        elsif ($code == 0x8A) {
            # 0x8A: // ActionWaitForFrame
            $action{'Frame'} = $reader->getUI16LE();
            $action{'SkipCount'} = $reader->getUI8();
        }
        elsif ($code == 0x8B) {
            # 0x8B: // ActionSetTarget
            $data = $reader->getData($length);
            @strs = split("\0", $data, 1+1);
            $action{'TargetName'} = $strs[0];
        }
        elsif ($code == 0x8C) {
            # 0x8C: // ActionSetTarget
            $data = $reader->getData($length);
            @strs = split("\0", $data, 1+1);
            $action{'Label'} = $strs[0];
        }
        elsif ($code == 0x8D) {
            # 0x8D: // ActionWaitForFrame2
            $action{'Frame'} = $reader->getUI16LE();
            $action{'SkipCount'} = $reader->getUI8();
        }
        elsif ($code == 0x96) {
            # 0x96: // ActionPush
            my $data = $reader->getData($length);
            my @values = ();
            my $values_reader = IO::SWF::Bit->new();
            $values_reader->input($data);
            while ($values_reader->hasNextData()) {
                my %value = ();
                my $type = $values_reader->getUI8();
                $value{'Type'} = $type;
                if ($type == 0) {
                    # 0: // STRING
                    $value{'String'} = IO::SWF::Type::String::parse($values_reader);
                }
                elsif ($type == 1) {
                    # 1: // Float
                    $value{'Float'} = IO::SWF::Type::Float::parse($values_reader);
                }
                elsif ($type == 2) {
                    # 2: // null
                    $value{'null'} = undef();
                }
                elsif ($type == 3) {
                    # 3: // undefined
                    $value{'undefined'} = undef();
                }
                elsif ($type == 4) {
                    # 4: // RegisterNumber
                    $value{'RegisterNumber'} = $values_reader->getUI8();
                }
                elsif ($type == 5) {
                    # 5: // Boolean
                    $value{'Boolean'} = $values_reader->getUI8();
                }
                elsif ($type == 6) {
                    # 6: // Double
                    $value{'Double'} = IO::SWF::Type::Double::parse($values_reader);
                }
                elsif ($type == 7) {
                    # 7: // Integer
                    $value{'Integer'} = $values_reader->getUI32LE();
                }
                elsif ($type == 8) {
                    # 8: // Constant8
                    $value{'Constant8'} = $values_reader->getUI8();
                }
                elsif ($type == 9) {
                    # 9: // Constant16
                    $value{'Constant16'} = $values_reader->getUI16LE();
                }
                else {
                    die "Illegal ActionPush value's type($type)";
                }
                push @values, \%value;
            }
            $action{'Values'} = \@values;
        }
        elsif ($code == 0x99) {
            # 0x99: // ActionJump
            $action{'BranchOffset'} = $reader->getSI16LE();
        }
        elsif ($code == 0x9A) {
            # 0x9A: // ActionGetURL2
            $action{'SendVarsMethod'} = $reader->getUIBits(2);
            $action{'(Reserved)'} = $reader->getUIBits(4);
            $action{'LoadTargetFlag'} = $reader->getUIBit();
            $action{'LoadVariablesFlag'} = $reader->getUIBit();
        }
        elsif ($code == 0x9D) {
            # 0x9D: // ActionIf
            $action{'Offset'} = $reader->getSI16LE();
        }
        elsif ($code == 0x9F) {
            # 0x9F: // ActionGotoFrame2
            $action{'(Reserved)'} = $reader->getUIBits(6);
            my $sceneBlasFlag = $reader->getUIBit();
            $action{'SceneBlasFlag'} = $sceneBlasFlag;
            $action{'PlayFlag'} =  $reader->getUIBit();
            if ($sceneBlasFlag == 1) {
                $action{'SceneBias'} = $reader->getUI16LE();
            }
        }
        else {
            $action{'Data'} =  $reader->getData($length);
        }
    }
    return \%action;
}

sub build {
    my ($writer, $action_href, $opts_href) = @_;
    my %action = ref($action_href) ? %{$action_href} : ();
    my ($data, $count);

    my $code = $action{'Code'};
    $writer->putUI8($code);
    if (0x80 <= $code) {
        if ($code == 0x81) {
            # 0x81: // ActionGotoFrame
            $writer->putUI16LE(2);
            $writer->putUI16LE($action{'Frame'});
        }
        elsif ($code == 0x83) {
            # 0x83: // ActionGetURL
            $data = $action{'UrlString'}."\0".$action{'TargetString'}."\0";
            $writer->putUI16LE(length($data));
            $writer->putData($data);
        }
        elsif ($code == 0x88) {
            # 0x88: // ActionConstantPool
            my @ConstantPool = @{$action{'ConstantPool'}};
            $count = scalar(@ConstantPool);
            $data = join("\0", @ConstantPool) . "\0";
            $writer->putUI16LE(length($data) + 2);
            $writer->putUI16LE($count);
            $writer->putData($data);
        }
        elsif ($code == 0x8A) {
            # 0x8A: // ActionWaitForFrame
            $writer->putUI16LE($action{'Frame'});
            $writer->putUI8($action{'SkipCount'});
        }
        elsif ($code == 0x8B) {
            # 0x8B: // ActionSetTarget
            $data = $action{'TargetName'}."\0";
            $writer->putUI16LE(length($data));
            $writer->putData($data);
        }
        elsif ($code == 0x8C) {
            # 0x8C: // ActionGoToLabel
            $data = $action{'Label'}."\0";
            $writer->putUI16LE(length($data));
            $writer->putData($data);
        }
        elsif ($code == 0x8D) {
            # 0x8D: // ActionWaitForFrame2
            $writer->putUI16LE($action{'Frame'});
            $writer->putUI8($action{'SkipCount'});
        }
        elsif ($code == 0x96) {
            # 0x96: // ActionPush
            my $values_writer = IO::SWF::Bit->new();
            foreach my $value (@{$action{'Values'}}) {
                my $type = $value->{'Type'};
                $values_writer->putUI8($type);
                if ($type == 0) {
                    # 0: // STRING
                    my $str = $value->{'String'};
                    my $pos = index($str, "\0");
                    if ($pos < 0) {
                        $str .= "\0";
                    } else {
                        $str = substr($str, 0, $pos + 1);
                    }
                    $values_writer->putData($str);
                }
                elsif ($type == 1) {
                    # 1: // Float
                    IO::SWF::Type::Float::build($values_writer, $value->{'Float'});
                }
                elsif ($type == 2) {
                    # 2: // null
                    # nothing to do.
                }
                elsif ($type == 3) {
                    # 3: // undefined
                    # nothing to do.
                }
                elsif ($type == 4) {
                    # 4: // RegisterNumber
                    $values_writer->putUI8($value->{'RegisterNumber'});
                }
                elsif ($type == 5) {
                    # 5: // Boolean
                    $values_writer->putUI8($value->{'Boolean'});
                }
                elsif ($type == 6) {
                    # 6: // Double
                    IO::SWF::Type::Double::build($values_writer, $value->{'Double'});
                }
                elsif ($type == 7) {
                    # 7: // Integer
                    $values_writer->putUI32LE($value->{'Integer'});
                }
                elsif ($type == 8) {
                    # 8: // Constant8
                    $values_writer->putUI8($value->{'Constant8'});
                }
                elsif ($type == 9) {
                    # 9: // Constant16
                    $values_writer->putUI16LE($value->{'Constant16'});
                }
                else {
                    die "Illegal ActionPush value's type($type)";
                }
            }
            my $values_data = $values_writer->output();
            $writer->putUI16LE(length($values_data));
            $writer->putData($values_data);
        }
        elsif ($code == 0x99) {
            # 0x99: // ActionJump
            $writer->putUI16LE(2);
            $writer->putSI16LE($action{'BranchOffset'});
        }
        elsif ($code == 0x9A) {
            # 0x9A: // ActionGetURL2
            $writer->putUI16LE(1);
            $writer->putUIBits($action{'SendVarsMethod'}, 2);
            $writer->putUIBits(0, 4); # Reserved
            $writer->putUIBit($action{'LoadTargetFlag'});
            $writer->putUIBit($action{'LoadVariablesFlag'});
        }
        elsif ($code == 0x9D) {
            # 0x9D: // ActionIf
            $writer->putUI16LE(2);
            $writer->putSI16LE($action{'Offset'});
        }
        elsif ($code == 0x9F) {
            # 0x9F: // ActionGotoFrame2
            my $sceneBlasFlag;
            if (defined ($action{'SceneBias'})) {
                $sceneBlasFlag = 1;
                $writer->putUI16LE(3);
            } else {
                $sceneBlasFlag = 0;
                $writer->putUI16LE(1);
            }
            $writer->putUIBits(0, 6); # Reserved
            $writer->putUIBit($sceneBlasFlag);
            $writer->putUIBit($action{'PlayFlag'});
            if ($sceneBlasFlag) {
                $writer->putUI16LE($action{'SceneBias'});
            }
        }
        else {
            $data = $action{'Data'};
            $writer->putUI16LE(length($data));
            $writer->putData($data);
        }
    }
}

sub string {
    my ($action_href, $opts_href) = @_;
    my %action = ref($action_href) ? %{$action_href} : ();

    my $code = $action{'Code'};
    my $str = sprintf('%s(Code=0x%02X)', getCodeName($code), $code);
    if (defined ($action{'Length'})) {
        $str .= sprintf(" (Length=%d):", $action{'Length'});
        $str .= "\n\t";
        if ($code == 0x88) {
            # 0x88: // ActonConstantPool
            $str .= " Count=".$action{'Count'}."\n";
            my @ConstantPool = @{$action{'ConstantPool'}};
            for(my $idx = 0; $idx < @ConstantPool;$idx++) {
                $str .= "\t[$idx] " . $ConstantPool[$idx] . "\n";
            }
        }
        elsif ($code == 0x96) {
            $str .= "   ";
            foreach my $value (@{$action{'Values'}}) {
                foreach my $key (keys %{$value}) {
                    $str .= " ($key)".$value->{$key} if $key ne 'Type';
                }
            }
        }
        else {
            my @data_keys = keys %action;
            foreach my $key (keys %action) {
                next if ($key eq 'Code' || $key eq 'Length');
                my $value = $action{$key};
                if (ref($value) eq 'HASH') {
                    my @new_value = ();
                    foreach my $l_key (keys %{$value}) {
                        push @new_value, $l_key . ":" . $value->{$l_key};
                    }
                    $value = join(' ', @new_value);
                }
                $str .= "   " ."$key=$value";
            }
        }
    }
    return $str;
}

1;