package Pcore::Selenium::Driver;

package    # hide from pause
  Selenium::Remote::Driver;

use Pcore;
no warnings qw[redefine];

# FIND_ELEMENT FAMILY
BEGIN {
    *__find_element        = \&find_element;
    *__find_elements       = \&find_elements;
    *__find_child_element  = \&find_child_element;
    *__find_child_elements = \&find_child_elements;
}

sub _parse_find_element_params {
    my $self   = shift;
    my $params = shift;

    my $driver_params = [];
    my $new_timeout;
    if ( scalar @{$params} == 1 ) {
        $driver_params = [ $params->[0], 'xpath' ];
    }
    elsif ( scalar @{$params} == 2 ) {
        $driver_params = [ $params->[1], $params->[0] ];
    }
    elsif ( scalar @{$params} == 3 ) {
        $driver_params = [ $params->[0], 'xpath' ];
        $new_timeout = $params->[2];
    }
    elsif ( scalar @{$params} == 4 ) {
        $driver_params = [ $params->[1], $params->[0] ];
        $new_timeout = $params->[3];
    }

    my $old_timeout;
    if ($new_timeout) {
        $old_timeout = $self->implicit_wait_timeout;
        if ( $old_timeout != $new_timeout ) {
            $self->implicit_wait_timeout($new_timeout);
        }
    }

    return {
        driver  => $driver_params,
        timeout => $old_timeout
    };
}

sub find_element {
    my $self = shift;
    P->log->sendlog( 'Pcore-Selenium', 'find element: ' . join ', ', @_ );
    my $params = $self->_parse_find_element_params( \@_ );
    my $res    = try {
        return __find_element( $self, @{ $params->{driver} } );
    }
    catch {
        my $err = shift;
        $self->implicit_wait_timeout( $params->{timeout} ) if defined $params->{timeout};
        die $err;
    };

    $self->implicit_wait_timeout( $params->{timeout} ) if defined $params->{timeout};
    return $res;
}

sub find_elements {
    my $self = shift;

    P->log->sendlog( 'Pcore-Selenium', 'find elements: ' . join ', ', @_ );

    my $params = $self->_parse_find_element_params( \@_ );

    my $res = try {
        return __find_elements( $self, @{ $params->{driver} } );
    }
    catch {
        my $err = shift;
        $self->implicit_wait_timeout( $params->{timeout} ) if defined $params->{timeout};
        die $err;
    };

    $self->implicit_wait_timeout( $params->{timeout} ) if defined $params->{timeout};
    if ( !defined $res ) {
        die 'Nothing found!';
    }
    else {
        return $res;
    }
}

sub find_child_element {
    my $self   = shift;
    my $parent = shift;

    P->log->sendlog( 'Pcore-Selenium', 'find child element: ' . join ', ', @_ );

    my $params = $self->_parse_find_element_params( \@_ );

    my $res = try {
        return \&__find_child_element( $self, $parent, @{ $params->{driver} } );
    }
    catch {
        my $err = $@;

        $self->implicit_wait_timeout( $params->{timeout} ) if defined $params->{timeout};

        die $err;
    };

    $self->implicit_wait_timeout( $params->{timeout} ) if defined $params->{timeout};

    return ${$res};
}

sub find_child_elements {
    my $self   = shift;
    my $parent = shift;

    P->log->sendlog( 'Pcore-Selenium', 'find child elements: ' . join ', ', @_ );

    my $params = $self->_parse_find_element_params( \@_ );

    my $res = try {
        return \&__find_element( $self, $parent, @{ $params->{driver} } );
    }
    catch {
        my $err = shift;
        $self->implicit_wait_timeout( $params->{timeout} ) if defined $params->{timeout};
        die $err;
    };

    $self->implicit_wait_timeout( $params->{timeout} ) if defined $params->{timeout};

    return return @{$res};
}

# FIND_ELEMENT ADDITIONAL METHODS
sub find_element_local {
    my $self   = shift;
    my $params = \@_;

    return try {
        return $self->find_element( @{$params} );
    }
    catch {
        return;
    };
}

sub find_elements_local {
    my $self   = shift;
    my $params = \@_;
    return try {
        return $self->find_elements( @{$params} );
    }
    catch {
        return;
    };
}

sub implicit_wait_timeout {
    my $self    = shift;
    my $timeout = shift;

    $self->{_implicit_wait_timeout} = 0 unless defined $self->{_implicit_wait_timeout};

    if ( defined $timeout ) {
        if ( $timeout != $self->{_implicit_wait_timeout} ) {
            $self->set_implicit_wait_timeout( 0+ $timeout );
            $self->{_implicit_wait_timeout} = $timeout;
        }
        return $timeout;
    }
    else {
        return $self->{_implicit_wait_timeout};
    }
}

sub set_window_maximized {
    my $self   = shift;
    my $window = shift || 'current';
    my $res    = { 'command' => 'setWindowMaximized', 'window_handle' => $window };
    my $ret    = $self->_execute_command($res);
    if ( $ret =~ /204/smg ) {
        return 1;
    }
    else {
        return 0;
    }
}

sub add_cookies {
    my $self    = shift;
    my $cookies = shift;

    if ( ref $cookies eq 'HASH' ) {
        $self->add_cookie( $cookies->{name}, $cookies->{value}, $cookies->{path}, $cookies->{domain}, $cookies->{secure} );
    }
    elsif ( ref $cookies eq 'ARRAY' ) {
        for my $cookie ( @{$cookies} ) {
            $self->add_cookie( $cookie->{name}, $cookie->{value}, $cookie->{path}, $cookie->{domain}, $cookie->{secure} );
        }
    }
    else {
        die 'Incorrect call';
    }

    return;
}

sub set_wmode {
    my $self = shift;
    my %args = (
        parent => undef,
        @_
    );

    my $js = <<'JS';
        var embeds = arguments[0] ? arguments[0].getElementsByTagName('embed') : document.getElementsByTagName('embed');
        for (i = 0; i < embeds.length; i++) {
            if (!embeds[i].getAttribute('wmode') || embeds[i].getAttribute('wmode').toLowerCase() == 'window'){
                var embed = embeds[i].cloneNode(true);
                embed.setAttribute('wmode', 'transparent');
                embeds[i].parentNode.replaceChild(embed, embeds[i]);
            }
        }
JS
    $self->execute_script( $js, $args{parent} );
    return $self;
}

sub screenshot {
    my $self = shift;
    my %args = (
        include_flash => 0,
        left          => 0,
        top           => 0,
        width         => undef,
        height        => undef,
        right         => undef,
        bottom        => undef,
        @_,
    );

    $self->set_wmode if $args{include_flash};

    my $res = { 'command' => 'screenshot' };
    my $screenshot = P->data->from_b64( $self->_execute_command($res) );

    if ( $args{left} || $args{top} || $args{right} || $args{bottom} || $args{width} || $args{height} ) {
        state $init = !!require Imager;

        my $img = Imager->new;

        $img->read( data => $screenshot, type => 'png' );

        my %params = (
            left   => $args{left},
            top    => $args{top},
            width  => 0,
            height => 0,
        );

        if ( $args{width} ) {
            $params{width} = $args{width};
        }
        elsif ( $args{right} ) {
            $params{width} = $args{right} - $args{left};
        }
        else {
            $params{width} = $img->getwidth - $args{left};
        }

        if ( $args{height} ) {
            $params{height} = $args{height};
        }
        elsif ( $args{bottom} ) {
            $params{height} = $args{bottom} - $args{top};
        }
        else {
            $params{height} = $img->getheight - $args{top};
        }

        my $el = $img->crop(%params);

        my $image;
        $el->write( data => \$image, type => 'png' );
        return { data => \$image, type => 'png', width => $el->getwidth, height => $el->getheight };
    }
    else {
        return { data => \$screenshot, type => 'png' };
    }
}

sub open_window {
    my $self = shift;
    my %args = (
        url        => undef,
        name       => '_blank',
        naked      => 0,          # disable all browser elements by default, each element visibility can be redefined individually
        width      => 100,
        height     => '100',
        left       => 0,
        location   => 1,
        menubar    => 1,
        resizable  => 1,
        scrollbars => 1,
        status     => 1,
        titlebar   => 1,
        toolbar    => 1,
        @_,
    );

    my $url  = delete $args{url}  // q[];
    my $name = delete $args{name} // q[];
    if ( my $naked = delete $args{naked} ) {
        P->hash->merge(
            \%args,
            {   location   => 0,
                menubar    => 0,
                resizable  => 0,
                scrollbars => 0,
                status     => 0,
                titlebar   => 0,
                toolbar    => 0,
            }
        );
    }
    my $params = join q[,], map {"$_=$args{$_}"} keys %args;
    say $params;
    if ( !$name || $name eq '_blank' ) {
        $name = 'w' . int rand 99_999_999;
    }

    $self->execute_script( 'window.open(arguments[0], arguments[1], arguments[2])', $url, $name, $params );
    $self->switch_to_window($name);
    my $handle = $self->get_current_window_handle;
    return $handle;
}

# TODO
# не пост запросы можно открывать в новом окне и затем получать контент окна
# при этом не сможем получить http хедеры
# проверить - передается ли реферер при открытии нового окна со ссылкой и без
# проверить - можем ли мы указывать реферер в ajax запросах
# ajax запросы могут не работать для других доменов, в таком случае получение контента возможно только через открытие нового окна, или через механизм onevent для ифреймов
# iframe onevent предпочтительнее, т.к. могут быть доступны хедеры ответа
# - добавить функционал запрещения кеширования
# - проеврить подмену реферера в ajax
# - проверить сохраниение реферера для нового окна с передачей url при открытии и методом get
# - решить проблему кроссдоменных запросов
sub _get_binary_data {
    my $self = shift;
    my $url  = shift;
    my %args = (
        post => undef,
        @_
    );

    # TODO maybe encoding needed
    if ( $args{post} ) {
        $args{post} = join q[&], map { $_ . q[=] . P->data->to_uri( $args{post}->{$_} ) } keys %{ $args{post} };
    }

    $self->set_async_script_timeout(50_000);
    my $js = <<'JS';
        var url = arguments[0];
        var options = arguments[1];
        var callback = arguments[arguments.length-1];
        var xhr = new XMLHttpRequest();
        xhr.overrideMimeType('text/plain; charset=x-user-defined');
        xhr.onreadystatechange = function(){
            if(xhr.readyState == 4){
                var binStr = xhr.responseText;
                var byte = new Array();
                for (var i = 0, len = binStr.length; i < len; ++i) {
                    var c = binStr.charCodeAt(i);
                    byte[i] = c & 0xff;
                }
                callback({
                    code:    xhr.status,
                    headers: xhr.getAllResponseHeaders(),
                    body:    byte
                });
            }
        }
        if(options.post){
            xhr.open('POST', url, true);
            xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
            xhr.send(options.post);
        }
        else{
            xhr.open('GET', url, true);
            xhr.send(null);
        }
JS
    my $callback = 'return arguments[0];';
    my $res = $self->execute_async_script( $js, $url, \%args, $callback );
    return unless $res->{code};

    my $body = q[];

    for ( $res->{body}->@* ) {
        $body .= chr;
    }

    my $r = HTTP::Response->parse( 'HTTP/1.0 ' . $res->{code} . q[ ] . $CRLF . $res->{headers} . $body );

    return $r;
}

sub DESTROY {
    my $self = shift;

    return if $$ != $self->{pid};

    if ( defined $self->{session_id} ) {
        if ( ${^GLOBAL_PHASE} eq 'DESTRUCT' ) {
            P->http->delete( 'http://' . $self->{remote_server_addr} . q[:] . $self->{port} . '/wd/hub/session/' . $self->{session_id} );
        }
        else {
            $self->quit() if $self->{auto_close};
        }
    }
    return;
}

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