package DTA::Controller::Search;
use Moose;
use namespace::autoclean;

BEGIN {extends 'Catalyst::Controller'; }

use utf8;
use JSON qw(to_json);
use List::MoreUtils qw(any uniq);
use Sphinx::Search;
use URI::Encode;

=head1 NAME

DTA::Controller::Web::Search - Catalyst Controller

=head1 DESCRIPTION

Catalyst Controller.

=head1 METHODS

=head2 index @deprecated


=cut

sub index :Path :Args(0) {
    my ( $self, $c ) = @_;

    if ( $c->req->params->{in} eq 'dtadoc' ) {
        $c->detach('doc');
    }
    elsif ( $c->req->params->{in} eq 'text' ) {
        # War die DDC Suche, die nun über das DWDS läuft.
        # $c->detach('/search/ddc/search');
        my $uri = URI::Encode->new( { encode_reserved => 0 } );
        my $u = 'https://www.dwds.de//r?corpus=dta&q=' . $c->req->params->{q};
        $u = $uri->encode($u);
        $c->res->redirect( $u, 301 );
    }
    else {
        $c->req->params->{'search'}   = 1;
        $c->req->params->{'search-1'} = $c->req->params->{q};
        $c->req->params->{'select-1'} = 'all';
        $c->detach('metadata');
    }
}

=head2 doc

=cut

sub doc :Local {
    my ( $self, $c ) = @_;
    my $query = $c->req->params->{q};

    my $sph = Sphinx::Search->new({ port => 9312, debug => 1 });
    my $results = $sph->SetMatchMode( SPH_MATCH_EXTENDED )
                      ->SetLimits( 0, 1000 )
                      ->SetFieldWeights({ section => 4, text => 1 })
                      ->SetSortMode(SPH_SORT_RELEVANCE)
                      ->Query($query);

    my @q_words = map { $_->{tokenized} } @{$sph->BuildKeywords($query, 'dtadoc')};

    my @ids = map { $_->{doc} } @{ $results->{matches} };
    my $matches = $c->model('DTADB::Dtadoc')->search({ id => { -in => \@ids } });

    $c->stash(
        excerpt  => sub {
            my $content = shift;
            return unless $content;
            my $excerpt = $sph->BuildExcerpts( [$content], 'dtadoc', join( ' ', map { s/\*//g; $_ } @q_words ), { before_match => '~~~', after_match => '!!!', around => 10 } );
            my $ret = $excerpt->[0];
            for ( $ret ) {
                s/&/&amp;/g;
                s/</&lt;/g;
                s/>/&gt;/g;
                s/~~~/<b>/g;
                s/!!!/<\/b>/g;
            }
            return $ret;
        },
        results  => [ $matches->all ],
        template => 'web/search/doc.tt',
    );
}

=head2 metadata

=cut

sub metadata :Local {
    my ( $self, $c ) = @_;
    if ( $c->req->params->{search} || $c->req->params->{corpus} ) {
        $c->forward('metadata_search');
    }
    $c->forward('metadata_form');
    $c->stash(
        template => 'web/search/metadata.tt',
    );
}

=head2 metadata_form

=cut

sub metadata_form :Private {
    my ( $self, $c ) = @_;
    my @fields = (
        [ all       => 'alle Felder',              1 ],
        [ person    => 'Person, Autor',            2 ],
        [ pnd       => 'GND-Nummer'                  ],
        [ title     => 'Titel',                    3 ],
        [ otitle    => 'Titel übergeordnetes Werk'   ],
        [ place     => 'Druckort'                    ],
        [ publisher => 'Verlag/Drucker'              ],
        [ genre     => 'Klassifikation',           4 ],
    );

    $c->stash(
        fields => \@fields,
    );
}

=head2 metadata_search

=cut

sub metadata_search :Private {
    my ( $self, $c ) = @_;

    my %query;
    {
        no warnings 'uninitialized';
        %query = map { ("search-$_" => clean_input($c->req->params->{"search-$_"})) } 1 .. 4;
        $query{year} = clean_input( $c->req->params->{year} );
        my @fonts = ref $c->req->params->{font} eq 'ARRAY' ? @{$c->req->params->{font}}
                  : exists $c->req->params->{font}         ? $c->req->params->{font}
                  :                                          ();
        @fonts = grep { /^(?:antiqua|fraktur|handschrift)$/ } @fonts;
        $query{font} = \@fonts;

        $query{corpus} = $c->req->params->{corpus};

        return if (grep { $_ ne "" } values %query) == 0;

        for my $i ( 1 .. 4 ) {
            $query{"field-$i"} = ( any { $c->req->params->{"select-$i"} eq $_ } qw(all person gnd title otitle place publisher genre) ) ? $c->req->params->{"select-$i"} : 'all';
        }

        for my $i ( 2 .. 4 ) {
            $query{"op-$i"} = ( any { $c->req->params->{"op-$i"} eq $_ } qw(and or not) ) ? $c->req->params->{"op-$i"} : 'and';
        }

        for my $i ( 1 .. 4 ) {
            $query{"sop-$i"} = ( ($query{"op-$i"} eq 'or') or ($query{"op-".($i+1)} eq 'or') ) ? 'or' : 'and';
        }
    }

    my %cond = ( -and => [
        'me.ready' => { -in => [ 1, 2 ] },
    ] );

    my %attr = (
        prefetch => [ 'source', { 'bookplace' => 'location' } ],
        order_by => [ 'autor1_lastname NULLS LAST', 'autor1_prename', 'title', 'band_zaehlung' ],
        cache    => 1,
    );
    my %search = (
        'and' => [],
        'or'  => [],
    );

    my @search_pat;

    for my $i ( 1 .. 4 ) {
        next unless $query{ "search-$i" } ne '';

        my $query = $query{ "search-$i" };
        my $op    = ($i == 1) ? 'and' : $query{"op-$i"};
        my $sop   = $query{ "sop-$i" };
        my $mop   = $op eq 'not' ? '-and' : '-or';
        my $field = $query{"field-$i"};

        my $like_op = $op eq 'not' ? '-not_ilike' : '-ilike';
        my $cmp_op  = $op eq 'not' ? '!='         : '=';

        push @search_pat, $query if $op ne 'not';

        if ( $field eq 'all' ) { # default
            # first, search if there was an autocomplete item selected (dta_bibl_angabe)
            my $direct = $c->model('DTADB::Book')->find({
                dta_bibl_angabe => $query,
                ready           => { -in => [1, 2] },
            });
            if ( $direct ) {
                $c->res->redirect( $c->uri_for('/book/show', $direct->id_book) );
                $c->detach;
            }

            # apply search
            my @cols = qw(
                me.autor1_prename me.autor1_lastname me.autor1_pnd me.autor1_syn_names
                me.autor2_prename me.autor2_lastname me.autor2_pnd me.autor2_syn_names
                me.autor3_prename me.autor3_lastname me.autor3_pnd me.autor3_syn_names
                me.title me.subtitle me.other_title me.dta_reihe_titel
                me.uebersetzer me.publisher me.dta_pub_location me.dta_pub_date
                me.dwds_kategorie1 me.dwds_unterkategorie1 me.dirname
                me.dta_bibl_angabe me.dta_comment2
            );

            my @ids;
            my @terms = map { s/^\s+//; s/\s+$//; $_ } split /\s+/, $query;
            foreach my $term ( @terms ) {
                my @or = map { $_ => { $like_op => "%$term%" } } @cols;

                my @title_ids = map { $_->book } $c->model('DTADB::MetaTitle')->search(
                    {
                        $mop => [
                            title     => { $like_op => "%$term%" },
                            normtitle => { $like_op => "%$term%" },
                        ]
                    },
                    {
                        select => [ 'book' ],
                        as     => [ 'book' ],
                        cache  => 1,
                    },
                )->all;
                push @ids, @title_ids;

                my @person_ids = map { $_->book } $c->model('DTADB::MetaPerson')->search(
                    {
                        $mop => [
                            name => { $like_op => "%$term%" },
                        ]
                    },
                    {
                        select => [ 'book' ],
                        as     => [ 'book' ],
                        cache  => 1,
                    },
                )->all;
                push @ids, @person_ids;
            }

            my $list_op = $op eq 'not' ? '-not_in' : '-in';
            foreach my $term ( @terms ) {
                my @or = map { $_ => { $like_op => "%$term%" } } @cols;
                push @or, 'me.id_book' => { $list_op => \@ids } if scalar @ids;
                push @{$search{$sop}}, $mop => \@or;
            }
        }
        elsif ( $field eq 'person' ) {
            my $orig_query = $query;
            if ( $query =~ /,/ ) {
                my ( $lastname, $surname ) = split /\s*,\s*/, $query;
                $query = "$surname $lastname";
            }
            my @ids = map { $_->book } $c->model('DTADB::MetaPerson')->search(
                {
                    -or => [
                        name => { -ilike => $query },
                        name => { -ilike => $orig_query },
                    ],
                },
                {
                    select => [ 'book' ],
                    as     => [ 'book' ],
                    cache  => 1,
                },
            )->all;

            my $list_op = $op eq 'not' ? '-not_in' : '-in';
            push @{$search{$sop}}, 'me.id_book' => { $list_op => \@ids } if scalar @ids;
            next if scalar @ids;

            my @or;

            for my $j ( 1 .. 3 ) {
                push @or, "me.autor$j".'_prename'   => { $like_op => "%$query%" };
                push @or, "me.autor$j".'_lastname'  => { $like_op => "%$query%" };
                push @or, "me.autor$j".'_syn_names' => { $like_op => "%$query%" };
            }
            push @or, 'me.uebersetzer' => { $like_op => "%$query%" };
            push @or, 'me.publisher'   => { $like_op => "%$query%" };
            push @{$search{$sop}}, $mop => \@or;
        }
        elsif ( $field eq 'pnd' ) {
            push @{$search{$sop}}, $mop => [ map { "me.autor$_".'_pnd' => { $cmp_op => $query } } 1 .. 3 ];
        }
        elsif ( $field eq 'title' ) {
            my @ids = map { $_->book } $c->model('DTADB::MetaTitle')->search(
                {
                    $mop => [
                        title     => { ilike => "%$query%" },
                        normtitle => { ilike => '%' . normalize($query) . '%' },
                    ]
                },
                {
                    select => [ 'book' ],
                    as     => [ 'book' ],
                    cache  => 1,
                },
            )->all;

            my $list_op = $op eq 'not' ? '-not_in' : '-in';
            push @{$search{$sop}}, 'me.id_book' => { $list_op => \@ids } if scalar @ids;
            next if scalar @ids;

            push @{$search{$sop}}, $mop => [
                'me.title'       => { $like_op => "%$query%" },
                'me.subtitle'    => { $like_op => "%$query%" },
                'me.other_title' => { $like_op => "%$query%" },
            ];
        }
        elsif ( $field eq 'otitle' ) {
            push @{$search{$sop}}, 'me.dta_reihe_titel' => { $like_op => "%$query%" };
        }
        elsif ( $field eq 'place' ) {
            push @{$search{$sop}}, 'me.dta_pub_location' => { $like_op => "%$query%" };
        }
        elsif ( $field eq 'publisher' ) {
            push @{$search{$sop}}, 'me.dta_pub_verlag' => { $like_op => "%$query%" };
        }
        elsif ( $field eq 'genre' ) {
            if ( $query =~ /(\S+)\s+::\s+(.+)/ ) { # pre-populated
                push @{$search{$sop}}, -and => [
                    'me.dwds_kategorie1'      => { $cmp_op => $1 },
                    'me.dwds_unterkategorie1' => { $cmp_op => $2 },
                ];
            }
            else {
                push @{$search{$sop}}, $mop => [
                    'me.dwds_kategorie1'      => { $like_op => "%$query%" },
                    'me.dwds_unterkategorie1' => { $like_op => "%$query%" },
                ];
            }
        }
    }

    # publication date
    if ( $query{year} ) {
        my $year = $query{year};
        $year =~ s/\s+//g;
        if ( $year =~ /([0-9]+)-([0-9]+)/ ) {
            push @{$search{'and'}}, 'me.dta_pub_date' => { -between => [ $1, $2 ] };
        }
        elsif ( $year =~ /([0-9]+)-(?![0-9])/ ) {
            push @{$search{'and'}}, 'me.dta_pub_date' => { '>=' => $1 };
        }
        elsif ( $year =~ /(?<![0-9])-([0-9]+)/ ) {
            push @{$search{'and'}}, 'me.dta_pub_date' => { '<=' => $1 };
        }
        else {
            push @{$search{'and'}}, 'me.dta_pub_date' => { '=' => $year };
        }
    }

    # corpus
    if ( $query{corpus} and $query{corpus} ne 'all' ) {
        if ( $query{corpus} eq 'core' ) {
            push @{$search{'and'}}, {
                -or => {
                    'source.source' => { -in => [ qw(don china kt) ] },
                    'me.dirname' => { -in => [ qw( goethe_reineke_1794 mesmer_magnetismus_1781 hoffmannswaldau_grabschriften_1662 nimwegen_friede_1679 humboldt_kosmos01_1845 humboldt_kosmos02_1847 humboldt_kosmos03_1850 humboldt_kosmos04_1858 humboldt_kosmos0501_1862 ) ] },
                },
            };
        }
        else {
            push @{$search{'and'}}, 'source.source' => $query{corpus};
        }
    }

    # font face
    if ( scalar @{$query{font}} == 1 ) {
        my $font = $query{font}->[0];
        push @{$search{'and'}}, 'me.schriftart' => { -ilike => "%$font%" };
    }

    push @{$cond{'-or'}}, @{$search{'or'}} if scalar @{$search{'or'}};

    if ( scalar @{$search{'and'}} ) {
        $cond{'-or'} = {};
        $cond{'-or'}{'-and'} = [];
        push @{$cond{'-or'}{'-and'}}, @{$search{'and'}};
    }

    my $list = $c->model('DTADB::Book')->search( \%cond, \%attr );

    if ( scalar @search_pat ) {
        $c->stash( search_pat => '(?i)' . join( '|' => map { quotemeta } @search_pat ) );
    }

    $c->stash(
        list => [ $list->all ],
    );
    $c->forward( 'metadata_form' );
}

sub clean_input {
    my $str = shift || '';
    for ( $str ) {
        s/^\s+//;
        s/\s+$//;
    }
    return $str;
}

sub normalize {
    my $str = shift;
    for ( $str ) {
        s/Ä/Ae/g;
        s/Ö/Oe/g;
        s/Ü/Ue/g;
        s/ä/ae/g;
        s/ö/oe/g;
        s/ü/ue/g;
        s/ß/ss/g;
    }
    return $str;
}

=head2 ac

=cut

sub ac :Local {
    my ( $self, $c ) = @_;

    my $field = $c->req->params->{field};
    my $term  = $c->req->params->{term};

    my @out;

    if ( $field eq 'person' ) {
        my @result = $c->model('DTADB::MetaPerson')->search(
            {
                name => { -ilike => "%$term%" },
            },
        )->all;
        @out = map { $_->name } @result;
    }
    elsif ( $field eq 'place' ) {
        my @result = $c->model('DTADB::Book')->search(
            {
                'me.dta_pub_location' => { -ilike => "%$term%" },
                'me.ready'            => { -in => [1, 2] },
            },
            {
                select => [ 'me.dta_pub_location' ],
                as     => [ 'dta_pub_location' ],
            },
        )->all;
        @out = map { $_->dta_pub_location } @result;
    }
    elsif ( $field eq 'title' ) {
        my @result = $c->model('DTADB::MetaTitle')->search(
            {
                -or => {
                    'me.title'     => { -ilike => "%$term%" },
                    'me.normtitle' => { -ilike => "%$term%" },
                }
            }
        )->all;
        @out = map { $_->title } @result;
    }
    elsif ( $field eq 'genre' ) {
        my @result = $c->model('DTADB::Book')->search(
            {
                'me.ready'   => { -in => [1, 2] },
                -or => {
                    'me.dwds_kategorie1'      => { -ilike => "%$term%" },
                    'me.dwds_unterkategorie1' => { -ilike => "%$term%" },
                }
            },
            {
                select   => [ qw(me.dwds_kategorie1 me.dwds_unterkategorie1) ],
                as       => [ qw(kategorie1 unterkategorie1) ],
            },
        )->all;
        @out = map { sprintf "%s :: %s", $_->dwds_kategorie1, $_->dwds_unterkategorie1 } @result;
    }
    elsif ( $field eq 'publisher' ) {
        my @result = $c->model('DTADB::Book')->search(
            {
                'me.dta_pub_verlag' => { -ilike => "%$term%" },
                'me.ready'          => { -in => [1, 2] },
            },
            {
                select => [ 'me.dta_pub_verlag' ],
                as     => [ 'dta_pub_verlag' ],
            },
        )->all;
        @out = map { $_->dta_pub_verlag } @result;
    }
    elsif ( $field eq 'bibl' ) {
        my @words = map { s/^\s+//; s/\s+$//; $_ } split /\s+/, $term;
        my %hits;
        foreach my $word ( @words ) {
            my @ids;
            my @titles = $c->model('DTADB::MetaTitle')->search(
                {
                    -or => {
                        'me.title'     => { -ilike => "%$word%" },
                        'me.normtitle' => { -ilike => "%$word%" },
                    }
                }
            )->all;
            push @ids, map { $_->get_column('book') } @titles;

            my @bibls = $c->model('DTADB::Book')->search(
                {
                    'me.dta_bibl_angabe' => { -ilike => "%$word%" },
                    'me.ready'           => { -in => [1, 2] },
                },
                {
                    select => [ 'me.id_book' ],
                    as     => [ 'me.id_book' ],
                },
            )->all;
            push @ids, map { $_->get_column('id_book') } @bibls;

            $hits{$_}++ for uniq @ids;
        }
        my @result;
        while( my ( $k, $v ) = each %hits ) {
            push @result, $k if $v == scalar @words;
        }
        @out = map { $_->dta_bibl_angabe } $c->model('DTADB::Book')->search({ 'me.id_book' => { -in => [ @result ] } })->all;
    }

    @out = grep { defined } (sort { $a cmp $b } uniq map { utf8::encode($_); $_ } @out)[0 .. 10];
    $c->res->content_type('application/json');
    $c->res->body( to_json(\@out) );
}

=head2

=cut

sub os :Local {
    my ( $self, $c, $corpus ) = @_;
    $c->res->redirect( "http://kaskade.dwds.de/dstar/$corpus" );
}

__PACKAGE__->meta->make_immutable;

1;
__END__

=head1 AUTHOR

Frank Wiegand

=head1 LICENSE

This library is free software. You can redistribute it and/or modify
it under the same terms as Perl itself.

=cut
