package Pcore::Handle::Selenium;

use Pcore -class, -autoload;
use Archive::Zip qw[];
use Selenium::Remote::Driver qw[];
use Selenium::Remote::WebElement qw[];
use Pcore::Selenium::Driver qw[];
use Pcore::Selenium::WebElement qw[];

with qw[Pcore::Core::H::Role::Wrapper];

has addr => ( is => 'ro', isa => Str, default => '//localhost:4444' );

has browsername => ( is => 'ro', isa => Enum [qw[firefox htmlunit]], default => 'firefox' );
has javascript => ( is => 'ro', isa => Bool, default => 1 );
has maximized  => ( is => 'ro', isa => Bool, default => 1 );

# proxy
has proxy_pac   => ( is => 'ro', isa => Str, default => q[] );
has proxy_http  => ( is => 'ro', isa => Str, default => q[] );
has proxy_https => ( is => 'ro', isa => Str, default => q[] );
has proxy_ftp   => ( is => 'ro', isa => Str, default => q[] );
has proxy_socks => ( is => 'ro', isa => Str, default => q[] );

# profile
has profile => ( is => 'lazy', isa => HashRef, default => sub { {} } );

# profile settings
has user_agent    => ( is => 'ro', isa => Str,  default => q[] );
has native_events => ( is => 'ro', isa => Bool, default => 1 );

# profile extensions
has noimages        => ( is => 'ro', isa => Bool,     default   => 0 );
has firebug         => ( is => 'ro', isa => Bool,     default   => 0 );
has noflash         => ( is => 'ro', isa => Bool,     default   => 1 );
has noscript        => ( is => 'ro', isa => Bool,     default   => 1 );
has allowed_scripts => ( is => 'ro', isa => ArrayRef, predicate => 1 );

has _implicit_wait_timeout => ( is => 'ro', isa => Int, default => 600_000 );

# http://code.google.com/p/selenium/wiki/JsonWireProtocol
# http://code.google.com/p/selenium/wiki/FirefoxDriver
# http://code.google.com/p/selenium/wiki/HtmlUnitDriver
# http://kb.mozillazine.org/About:config_entries
# http://habrahabr.ru/post/130912/
# http://seleniumhq.org/docs/04_webdriver_advanced.html
our $DEFAULT_FIREFOX_PROFILE = {
    'webdriver_accept_untrusted_certs'    => 'true',
    'webdriver_enable_native_events'      => 'true',
    'webdriver_assume_untrusted_issuer'   => 'true',
    'extensions.firebug.showFirstRunPage' => 'false',
    'extensions.firebug.defaultPanelName' => '"console"',
    'flashblock.silverlight.blocked'      => 'true',
    'noscript.firstRunRedirection'        => 'false',
    'noscript.global'                     => 'false',
};
our $STORED_WINDOW_HANDLES = [];

sub h_connect {
    my $self = shift;

    my $proxy = q[];
    if ( $self->proxy_http || $self->proxy_https || $self->proxy_ftp ) {
        $proxy = {
            proxyType => 'manual',
            $self->proxy_http  ? ( httpProxy => $self->proxy_http )  : (),
            $self->proxy_https ? ( sslProxy  => $self->proxy_https ) : (),
            $self->proxy_ftp   ? ( ftpProxy  => $self->proxy_ftp )   : (),
        };
    }
    elsif ( $self->proxy_pac ) {
        $proxy = {
            proxyType          => 'pac',
            proxyAutoconfigUrl => $self->proxy_pac
        };
    }

    my $uri = P->uri( $self->addr );

    my $h = Selenium::Remote::Driver->new(
        remote_server_addr => $uri->host,
        port               => $uri->port,
        accept_ssl_certs   => 1,
        auto_close         => 1,
        javascript         => $self->javascript,
        browser_name       => $self->browsername,
        platform           => 'ANY',
        $proxy ? ( proxy => $proxy ) : (),
        extra_capabilities => { $self->browsername eq 'firefox' ? ( firefox_profile => $self->_get_firefox_profile ) : () }
    );

    $h->{commands}->{setWindowMaximized} = {
        'method' => 'POST',
        'url'    => 'session/:sessionId/window/:windowHandle/maximize'
    };

    $h->set_window_maximized if $self->maximized;
    $h->implicit_wait_timeout( $self->_implicit_wait_timeout );

    return $h;
}

sub h_disconnect {
    my $self = shift;

    $self->h->quit;

    return;
}

sub _get_firefox_profile {
    my $self = shift;

    my $profile = P->hash->merge( $DEFAULT_FIREFOX_PROFILE, $self->profile );

    # set proxy socks via profile if needed
    if ( $self->proxy_socks ) {
        my ( $host, $port ) = split /:/sm, $self->proxy_socks;
        P->hash->merge(
            $profile,
            {   'network.proxy.socks'            => qq["$host"],
                'network.proxy.socks_port'       => $port,
                'network.proxy.socks_remote_dns' => 'true',
                'network.proxy.type'             => '1'
            }
        );
    }

    # user agent
    if ( $self->user_agent ) {
        P->hash->merge( $profile, { 'general.useragent.override' => q["] . $self->user_agent . q["] } );
    }

    # native events
    $profile->{'webdriver_enable_native_events'} = $self->native_events ? 'true' : 'false';

    # noimages
    P->hash->merge( $profile, { 'permissions.default.image' => 2 } ) if $self->noimages;

    # firebug
    # https://getfirebug.com/wiki/index.php/Firebug_Preferences
    if ( $self->firebug ) {
        P->hash->merge(
            $profile,
            {   'extensions.firebug.commandEditor'       => 'true',
                'extensions.firebug.console.enableSites' => 'true',
                'extensions.firebug.cookies.enableSites' => 'true',
                'extensions.firebug.net.enableSites'     => 'true',
                'extensions.firebug.script.enableSites'  => 'true',
                'extensions.firebug.allPagesActivation'  => q["none"],    # "on", "none"
            }
        );
    }
    else {
        P->hash->merge(
            $profile,
            {   'extensions.firebug.commandEditor'       => 'false',
                'extensions.firebug.console.enableSites' => 'false',
                'extensions.firebug.cookies.enableSites' => 'false',
                'extensions.firebug.net.enableSites'     => 'false',
                'extensions.firebug.script.enableSites'  => 'false',
                'extensions.firebug.allPagesActivation'  => q["none"],    # "on", "none"
            }
        );
    }

    # noflash
    if ( $self->noflash ) {
        P->hash->merge( $profile, { 'flashblock.enabled' => 'true' } );
    }
    else {
        P->hash->merge( $profile, { 'flashblock.enabled' => 'false' } );
    }

    # noscript
    if ( $self->noscript ) {
        P->hash->merge( $profile, { 'noscript.global' => 'false' } );
    }
    else {
        P->hash->merge( $profile, { 'noscript.global' => 'true' } );
    }
    P->hash->merge( $profile, { 'capability.policy.maonoscript.sites' => q["] . join( q[ ], @{ $self->allowed_scripts } ) . q["] } ) if $self->has_allowed_scripts;

    my $user_js = q[];
    for my $key ( sort keys %{$profile} ) {
        $user_js .= qq[user_pref("$key", $profile->{$key});\n];
    }

    my $profile_zip = q[];
    open my $profile_zip_fh, '>', \$profile_zip or die;
    my $zip = Archive::Zip->new;
    $zip->addString( $user_js, 'user.js' )->desiredCompressionMethod(Archive::Zip::COMPRESSION_DEFLATED);
    $zip->writeToFileHandle($profile_zip_fh);
    close $profile_zip_fh or die;
    return P->data->to_b64($profile_zip);
}

sub _AUTOLOAD ( $self, $method, @ ) {
    return <<"PERL";
        sub {
            my \$self = shift;

            return \$self->h->$method(\@_);
        };
PERL
}

sub ping {
    my $self = shift;

    $self->h->find_element_local( id => 'ping', timeout => 1 );

    return;
}

sub load_jquery {
    my $self = shift;

    $self->h->set_async_script_timeout(5000);
    my $js = <<'JS';
        var callback = arguments[arguments.length-1];
        if(!window.jQuery){
            var js = document.createElement('script');
            js.id = 'jQuery';
            js.async = true;
            js.src = 'http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js';
            js.onload = function(){
                callback(1);
            };
            document.getElementsByTagName('head')[0].appendChild(js);
        }
        else {
            callback(1);
        }
JS
    my $callback = 'return arguments[0];';
    return $self->h->execute_async_script( $js, $callback );
}

sub store_window_handles {
    my $self = shift;

    @{$STORED_WINDOW_HANDLES} = $self->h->get_window_handles;

    return $self;
}

sub switch_to_new_window {
    my $self = shift;

    my @window_handles = $self->h->get_window_handles;
    for my $new_handle (@window_handles) {
        unless ( $new_handle ~~ @{$STORED_WINDOW_HANDLES} ) {
            $STORED_WINDOW_HANDLES = [];
            my $current_handle = $self->h->get_current_window_handle;
            $self->h->switch_to_window($new_handle);
            return $current_handle;
        }
    }

    $STORED_WINDOW_HANDLES = [];
    return;
}

# TODO implement
sub wait_for_js_function {
    my $self    = shift;
    my $func    = shift;
    my $timeout = shift;

    ...;    ## no critic qw[ControlStructures::ProhibitYadaOperator]

    #    for (1 .. $timeout / 100) {
    #        my $res = $self->get_eval("typeof( window.$func )");
    #        return $res if $res ne 'null';
    #        $self->pause(100);
    #    }
    #    die "Timed out waiting for JS func: '$func'";
    return;
}

# DEPRECATED METHODS
sub set_implicit_wait_timeout {
    my $self = shift;
    die 'Method is deprecated';
}

sub find_child_element {
    my $self = shift;
    die 'Method is deprecated';
}

sub find_child_elements {
    my $self = shift;
    die 'Method is deprecated';
}

sub find_child_element_local {
    my $self = shift;
    die 'Method is deprecated';
}

sub find_child_elements_local {
    my $self = shift;
    die 'Method is deprecated';
}

1;
## -----SOURCE FILTER LOG BEGIN-----
##
## PerlCritic profile "pcore-script" policy violations:
## +------+----------------------+----------------------------------------------------------------------------------------------------------------+
## | Sev. | Lines                | Policy                                                                                                         |
## |======+======================+================================================================================================================|
## |    3 | 198                  | Subroutines::ProhibitUnusedPrivateSubroutines - Private subroutine/method '_AUTOLOAD' declared but not used    |
## +------+----------------------+----------------------------------------------------------------------------------------------------------------+
##
## -----SOURCE FILTER LOG END-----
__END__
=pod

=encoding utf8

=head1 CODING RULES

- sleep убрать везде, перейти на implicit wait;

- xpath использовать в крайних случаях, если нет возможности найти элемент по id, name ...;

- если нужен клик по элементу - сначала смотрим, что клик делает, пытаемся выполнить прямое действие (вызов js функции, сабмит формы, ...), только если прямого действия нет - ищем элемент и кливаем;

- использовать $el->trigger('click') вместо $el->click;

$self->find_element('XPATH');

$self->find_element(id => '<ID>');

$self->find_element(name => '<ID>');

... etc ...

$self->find_element('XPATH', timeout => XXX); - set implicit wait timeout before performing find_element, restore previous implicit wait timeout when done

$self->find_element(id => '<ID>', timeout => XXX); - set implicit wait timeout before performing find_element, restore previous implicit wait timeout when done

$self->implicit_wait_timeout - return current timeout

$self->implicit_wait_timeout(XXX) - set current timeout to XXX and return XXX

Direct call $self->set_implicit_wait_timeout IS DEPRECATED

=cut
