#! /usr/bin/perl
package PerlACE::TestTarget_Android;

# ******************************************************************
# Description : Creates a PerlACE::Android
# Author      : Marcel Smit
# Create Date : 29/20/2008
# ******************************************************************

# ******************************************************************
# Pragma Section
# ******************************************************************

use strict;

use PerlACE::TestTarget;
use PerlACE::ProcessVX;
use File::Copy;
use File::Glob;
use File::Spec;
use File::Basename;
use Cwd;
use English;

use POSIX "sys_wait_h";
require PerlACE::ProcessAndroid;

our @ISA = qw(PerlACE::TestTarget);

$PerlACE::TestTarget_Android::EMULATOR_RUNNING = 0;
$PerlACE::TestTarget_Android::LIBS = ();

sub new
{
    my $proto = shift;
    my $config_name = shift;
    my $component = shift;

    if (defined $ENV{'ACE_TEST_VERBOSE'}) {
        print STDERR "New Android target: $config_name : $component\n";
    }

    my $class = ref ($proto) || $proto;
    my $self = {};
    bless ($self, $class);

    $self->GetConfigSettings($config_name);
    $self->{FSROOT} = $ENV{'ANDROID_FS_ROOT'};
    $self->{PROCESS} = undef;
    $self->{RUNNING} = 0;

    # Start the target.
    if ($PerlACE::TestTarget_Android::EMULATOR_RUNNING == 0) {
        $self->start_target ();
    }

    return $self;
}

sub DESTROY
{
    my $self = shift;
    if ($self->{RUNNING} == 1) {
        # kill the emulator. No need to shutdown gracefully.
        if (defined $ENV{'ACE_TEST_VERBOSE'}) {
            print STDERR "Killing the Android emulator\n";
        }
        $self->KillAll ('emulator*');
        $self->KillAll ('adb');
        $PerlACE::TestTarget_Android::EMULATOR_RUNNING = 0;
        $PerlACE::TestTarget_Android::LIBS = ();
    }
}

# ******************************************************************
# Subroutine Section
# ******************************************************************

sub LocalFile
{
    my $self = shift;
    my $file = shift;

    my $newfile = $self->{FSROOT} . "/" . $file;
    if (defined $ENV{'ACE_TEST_VERBOSE'}) {
        print STDERR "Android LocalFile for $file is $newfile\n";
    }
    return $newfile;
}

sub AddLibPath ($)
{
    my $self = shift;
    my $dir = shift;
    my $noarch = shift;

    # If we have -Config ARCH, use the -ExeSubDir setting as a sub-directory
    # of the lib path.  This is in addition to the regular LibPath.
    if (!$noarch && defined $self->{ARCH}) {
        $self->AddLibPath($dir, 1);
        $dir .= '/' . $self->{EXE_SUBDIR};
    }

    if (defined $ENV{'ACE_TEST_VERBOSE'}) {
        print STDERR "Adding libpath $dir\n";
    }
    $self->{LIBPATH} = PerlACE::concat_path ($self->{LIBPATH}, $dir);
}

sub CreateProcess
{
    my $self = shift;
    my $process = new PerlACE::ProcessAndroid ($self, @_);
    return $process;
}

sub NeedReboot ($)
{
    my $self = shift;
    $self->{REBOOT_NEEDED} = 1;
}

# Reboot target
sub RebootNow ($)
{
    my $self = shift;
    $self->{REBOOT_NEEDED} = undef;
    print STDERR "Attempting to reboot target...\n";
    $self->reboot ();
}

sub start_target ()
{
    # For now, we're assuming one target (avd) is running in the test environment.
    # Need to change this when more than one avd's need to start
    my $self = shift;
    my $silent;

    if (!defined $ENV{'ACE_TEST_VERBOSE'}) {
      $silent = "2> /dev/null"
    }

    if (! defined ($ENV{'ANDROID_SDK_ROOT'})) {
        print STDERR "Error: Android SDK root not defined.\n";
        return 0;
    }
    if (! defined ($ENV{'ANDROID_AVD_NAME'})) {
        print STDERR "Error: Android AVD name not defined.\n";
        return 0;
    }
    my $avd_name = $ENV{'ANDROID_AVD_NAME'};
    my $android_process = $ENV{'ANDROID_SDK_ROOT'} . "/tools/android";
    my $avd_process = $ENV{'ANDROID_SDK_ROOT'} . "/tools/emulator";
    my $adb_process = $ENV{'ANDROID_SDK_ROOT'} . "/platform-tools/adb";
    my $user_data_image = $ENV{'ANDROID_SDK_HOME'} . "/.android/avd/" . $avd_name . ".avd/userdata-qemu.img";

    my $avd_options = "-noaudio -no-window -wipe-data";

    if (defined ($ENV{'ANDROID_AVD_OPTIONS'})) {
        print STDERR "Resetting AVD options\n";
        $avd_options = $ENV{'ANDROID_AVD_OPTIONS'};
    }

    $self->KillAll ("emulator*");

    FORK: {
        if ($self->{PROCESS} = fork) {
            #parent here
            bless $self;
        }
        elsif (defined $self->{PROCESS}) {
           #child here
           my $user_image_cmd = "rm -f " . $user_data_image;
           if (defined $ENV{'ACE_TEST_VERBOSE'}) {
               print STDERR "Removing user data image: $user_image_cmd\n";
           }

           system ( $user_image_cmd );
           if ($? != 0) {
               print STDERR "failed to execute: $!\n";
           }

           my $avd_cmd = "$avd_process" .' -avd ' .  "$avd_name $avd_options";
           if (defined $ENV{'ACE_TEST_VERBOSE'}) {
               print STDERR "Starting emulator cmd: $avd_cmd\n";
           }

           system ( $avd_cmd );
           if ($? != 0) {
               print STDERR "failed to execute: $!\n";
           }
           exit;
       }
       elsif ($! =~ /No more process/) {
            #EAGAIN, supposedly recoverable fork error
            sleep 5;
            redo FORK;
       }
       else {
           # weird fork error
           print STDERR "ERROR: Can't fork <" . $avd_process . ">: $!\n";
       }
    }

    eval {
        my $timeout = $self->AdbWaitForDeviceTimeout ();

        local $SIG{ALRM} = sub { die "alarm\n" }; # NB: \n required
        alarm $timeout;
        # start the waiting...
        my $wait_cmd = $adb_process . ' wait-for-device';
        system ( $wait_cmd );
        # reset alarm
        alarm 0;
    };

    if ($@) {
        # timed out
        exit unless $@ eq "alarm\n";   # propagate unexpected errors
    }
    else {
        if (defined $ENV{'ACE_TEST_VERBOSE'}) {
            print STDERR "Emulator is running <$self->{PROCESS}> -> start the tests.\n";
        }
    }


    # AVD is up and running and ready to spawn executables.
    # First some preparation.
    my $cmd = $adb_process . ' shell "mkdir ' . $self->{FSROOT} . '/tmp "' . $silent;
    if (defined $ENV{'ACE_TEST_VERBOSE'}) {
        print STDERR "Start to execute : $cmd\n";
    }
    system ( $cmd );

    $self->{RUNNING} = 1;
    $PerlACE::TestTarget_Android::EMULATOR_RUNNING = 1;
    return 0;
}

sub WaitForFileTimed ($)
{
    my $self = shift;
    my $file = shift;
    my $timeout = shift;
    my $silent;

    if (!defined $ENV{'ACE_TEST_VERBOSE'}) {
      $silent = ' > /dev/null 2>&1';
    }

    if ($PerlACE::Process::WAIT_DELAY_FACTOR > 0) {
        $timeout *= $PerlACE::Process::WAIT_DELAY_FACTOR;
    }

    my $newfile = $self->LocalFile ($file);
    if (defined $ENV{'ACE_TEST_VERBOSE'}) {
        print STDERR "Android waits $timeout seconds for $newfile\n";
    }

    # Since the file is available on the target (which we cannot reach),
    # we will try to pull the file from the target to a local directory.
    # If succeed, the the file is there an we can continue.
    my $adb_process = $ENV{'ANDROID_SDK_ROOT'} . "/platform-tools/adb";
    my $fsroot_target = $self->{FSROOT};

    my $cmd_copy_ior = $adb_process . ' pull ' . $newfile . ' ' .
                          File::Spec->tmpdir() . '/' .
                          basename ($newfile) . $silent;

    while ($timeout-- != 0) {
        # copy the ior back to the host sytem
        if (system ( $cmd_copy_ior ) == 0) {
            if (defined $ENV{'ACE_TEST_VERBOSE'}) {
                print STDERR "Pull $newfile succeeded\n";
            }
            return 0;
        }
        sleep (1);
    }
    return -1;
}

sub DeleteFile ($)
{
    my $self = shift;
    my $file = shift;
    my $adb_process = $ENV{'ANDROID_SDK_ROOT'} . "/platform-tools/adb";
    my $silent;

    if (!defined $ENV{'ACE_TEST_VERBOSE'}) {
      $silent = ' > /dev/null 2>&1';
    }

    my $targetfile = $self->LocalFile ($file);
    my $cmd = "$adb_process" . ' shell rm '. "$targetfile" . $silent;

    if (defined $ENV{'ACE_TEST_VERBOSE'}) {
      print STDERR "DeleteFile cmd: $cmd\n";
    }

    system ( $cmd );
    if ($? != 0) {
        return -1;
    }
    return 0;
}

sub Kill ()
{
    my $self = shift;

    if ($self->{RUNNING} == 1) {
        if (defined $ENV{'ACE_TEST_VERBOSE'}) {
            print STDERR "Killing emulator process <$self->{PROCESS}>\n";
        }

        my $cnt = kill (1, $self->{PROCESS});

        waitpid ($self->{PROCESS}, WNOHANG);
        if (defined $ENV{'ACE_TEST_VERBOSE'}) {
            print STDERR "Killed $cnt process(es)\n";
        }
        # $self->check_return_value ($?);
    }

    $self->{RUNNING} = 0;
}

sub KillAll ($)
{
    my $self = shift;
    my $procmask = shift;
    if ($OSNAME eq 'MSWin32') {
        if (defined $ENV{'ACE_TEST_VERBOSE'}) {
            print STDERR "Killall not implemented for Windows\n";
            return;
        }
    }
    else {
        my $cmd_killall = "killall -q -r $procmask";
        system ( $cmd_killall );
    }
}

sub PutFile ($)
{
    my $self = shift;
    my $src = shift;
    my $silent;

    if (!defined $ENV{'ACE_TEST_VERBOSE'}) {
      $silent = "2> /dev/null"
    }

    my $adb_process = $ENV{'ANDROID_SDK_ROOT'} . "/platform-tools/adb";

    my $targetfile = $self->LocalFile ($src);
    my $cmd = "$adb_process" . ' push '. "\"$src\" \"$targetfile\" $silent";

    if (defined $ENV{'ACE_TEST_VERBOSE'}) {
      print STDERR "PutFile cmd: $cmd\n";
    }

    system ( $cmd );
    if ($? != 0) {
        return -1;
    }
    return 0;
}

sub GetFile ($)
{
    my $self = shift;
    my $remote_file = shift;
    my $local_file = shift;
    my $silent;

    if (!defined $ENV{'ACE_TEST_VERBOSE'}) {
      $silent = "2> /dev/null"
    }

    my $adb_process = $ENV{'ANDROID_SDK_ROOT'} . "/platform-tools/adb";

    if (!defined $local_file) {
        $local_file = $remote_file;
    }
    $remote_file = $self->LocalFile($remote_file);
    my $cmd = "$adb_process" . ' pull '. "$remote_file $local_file $silent";

    if (defined $ENV{'ACE_TEST_VERBOSE'}) {
      print STDERR "GetFile cmd: $cmd\n";
    }

    system ( $cmd );
    if ($? != 0) {
        return -1;
    }
    return 0;
}

sub PutLib ($)
{
    my $self = shift;
    my $newlib = shift;
    my $tgtlib = shift;
    my $silent;

    if (!defined $ENV{'ACE_TEST_VERBOSE'}) {
      $silent = "2> /dev/null"
    }

    foreach my $lib (@{$PerlACE::TestTarget_Android::LIBS}) {
        if ($lib eq $newlib) {
            if (defined $ENV{'ACE_TEST_VERBOSE'}) {
                print STDERR "Duplicate lib $newlib\n";
            }
            return 0;
        }
    }

    my $adb_process = $ENV{'ANDROID_SDK_ROOT'} . "/platform-tools/adb";

    my $cmd = "$adb_process" . ' push '. "\"$newlib\" \"$tgtlib\" $silent";

    if (defined $ENV{'ACE_TEST_VERBOSE'}) {
      print STDERR "PutLib cmd: $cmd\n";
    }

    system ( $cmd );
    if ($? != 0) {
        return -1;
    }

    # keep tabs on copied libs
    push(@{$PerlACE::TestTarget_Android::LIBS}, $newlib);

    return 0;
}

1;

