%# BEGIN BPS TAGGED BLOCK {{{
%# 
%# COPYRIGHT:
%# 
%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
%#                                          <jesse@bestpractical.com>
%# 
%# (Except where explicitly superseded by other copyright notices)
%# 
%# 
%# LICENSE:
%# 
%# This work is made available to you under the terms of Version 2 of
%# the GNU General Public License. A copy of that license should have
%# been provided with this software, but in any event can be snarfed
%# from www.gnu.org.
%# 
%# This work is distributed in the hope that it will be useful, but
%# WITHOUT ANY WARRANTY; without even the implied warranty of
%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
%# General Public License for more details.
%# 
%# You should have received a copy of the GNU General Public License
%# along with this program; if not, write to the Free Software
%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
%# 02110-1301 or visit their web page on the internet at
%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
%# 
%# 
%# CONTRIBUTION SUBMISSION POLICY:
%# 
%# (The following paragraph is not intended to limit the rights granted
%# to you to modify and distribute this software under the terms of
%# the GNU General Public License and is only of importance to you if
%# you choose to contribute your changes and enhancements to the
%# community by submitting them to Best Practical Solutions, LLC.)
%# 
%# By intentionally submitting any modifications, corrections or
%# derivatives to this work, or any other work intended for use with
%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
%# you are the copyright holder for those contributions and you grant
%# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
%# royalty-free, perpetual, license to use, copy, create derivative
%# works based on those contributions, and sublicense and distribute
%# those contributions and any derivatives thereof.
%# 
%# END BPS TAGGED BLOCK }}}
%#
%# Data flow here:
%#   The page receives a Query from the previous page, and maybe arguments
%#   corresponding to actions.  (If it doesn't get a Query argument, it pulls
%#   one out of the session hash.  Also, it could be getting just a raw query from
%#   Build/Edit.html (Advanced).)
%#
%#   After doing some stuff with default arguments and saved searches, the ParseQuery
%#   function (which is similar to, but not the same as, _parser in RT/Tickets_Overlay_SQL)
%#   converts the Query into a RT::Interface::Web::QueryBuilder::Tree.  This mason file
%#   then adds stuff to or modifies the tree based on the actions that had been requested
%#   by clicking buttons.  It then calls GetQueryAndOptionList on the tree to generate
%#   the SQL query (which is saved as a hidden input) and the option list for the Clauses
%#   box in the top right corner.
%#
%#   Worthwhile refactoring: the tree manipulation code for the actions could use some cleaning
%#   up.  The node-adding code is different in the "add" actions from in ParseQuery, which leads
%#   to things like ParseQuery correctly not quoting numbers in numerical fields, while the "add"
%#   action does quote it (this breaks SQLite).
%#
<& /Elements/Header, Title => $title &>
<& /Ticket/Elements/Tabs, 
    current_tab => "Search/Build.html?".$QueryString, 
    Title => $title,
    %query,
    SavedSearchId => $saved_search{'Id'},
&>

<form method="post" action="Build.html" name="BuildQuery">
<input type="hidden" class="hidden" name="SavedSearchId" value="<% $saved_search{'Id'} %>" />
<input type="hidden" class="hidden" name="Query" value="<% $query{'Query'} %>" />
<input type="hidden" class="hidden" name="Format" value="<% $query{'Format'} %>" />




<div id="pick-criteria">
    <& Elements/PickCriteria, query => $query{'Query'}, cfqueues => $queues &>
</div>
<& /Elements/Submit,  Label => loc('Add these terms'), Name => 'AddClause'&>
<& /Elements/Submit, Label => loc('Add these terms and Search'), Name => 'DoSearch'&>


<div id="editquery">
<& Elements/EditQuery,
    %ARGS,
    actions => \@actions,
    optionlist => $optionlist,
    Description => $saved_search{'Description'},
    &>
</div>
<div id="editsearches">
    <& Elements/EditSearches, %saved_search, CurrentSearch => \%query &>
</div>

<span id="display-options">
<& Elements/DisplayOptions,
    %ARGS, %query,
    AvailableColumns => $AvailableColumns,
    CurrentFormat    => $CurrentFormat,
&>
<& /Elements/Submit, Label => loc('Update format and Search'), Name => 'DoSearch', id=>"formatbuttons"&>
</span>
</form>

<%INIT>
use RT::Interface::Web::QueryBuilder;
use RT::Interface::Web::QueryBuilder::Tree;

my $title = loc("Query Builder");

my %query;
for( qw(Query Format OrderBy Order RowsPerPage) ) {
    $query{$_} = $ARGS{$_};
}

my %saved_search;
my @actions = $m->comp( 'Elements/EditSearches:Init', %ARGS, Query => \%query, SavedSearch => \%saved_search);

if ( $NewQuery ) {

    # Wipe all data-carrying variables clear if we want a new
    # search, or we're deleting an old one..
    %query = ();
    %saved_search = ( Id => 'new' );

    # ..then wipe the session out..
    delete $session{'CurrentSearchHash'};

    # ..and the search results.
    $session{'tickets'}->CleanSlate if defined $session{'tickets'};
}

{ # Attempt to load what we can from the session and preferences, set defaults

    my $current = $session{'CurrentSearchHash'};
    my $prefs = $session{'CurrentUser'}->UserObj->Preferences("SearchDisplay") || {};
    my $default = { Query => '', Format => '', OrderBy => 'id', Order => 'ASC', RowsPerPage => 50 };

    for( qw(Query Format OrderBy Order RowsPerPage) ) {
        $query{$_} = $current->{$_} unless defined $query{$_};
        $query{$_} = $prefs->{$_} unless defined $query{$_};
        $query{$_} = $default->{$_} unless defined $query{$_};
    }

    for( qw(Order OrderBy) ) {
        if (ref $query{$_} eq "ARRAY") {
            $query{$_} = join( '|', @{ $query{$_} } );
        }
    }
    if ( $query{'Format'} ) {
        # Clean unwanted junk from the format
        $query{'Format'} = $m->comp( '/Elements/ScrubHTML', Content => $query{'Format'} );
    }
}

my $ParseQuery = sub {
    my ($string, $results) = @_;

    my $tree = RT::Interface::Web::QueryBuilder::Tree->new('AND');
    @$results = $tree->ParseSQL( Query => $string, CurrentUser => $session{'CurrentUser'} );

    return $tree;
};

my $tree = $ParseQuery->( $query{'Query'}, \@actions );

# if parsing went poorly, send them to the edit page to fix it
if ( $actions[0] ) {
    return $m->comp( "Edit.html", Query => $query{'Query'}, actions => \@actions );
}

my @options = $tree->GetDisplayedNodes;
my @current_values = grep defined, @options[@clauses];
my @new_values = ();

# {{{ Try to find if we're adding a clause
foreach my $arg ( keys %ARGS ) {
    next unless $arg =~ m/^ValueOf(\w+|'CF.{.*?}')$/
                && ( ref $ARGS{$arg} eq "ARRAY"
                     ? grep $_ ne '', @{ $ARGS{$arg} }
                     : $ARGS{$arg} ne '' );

    # We're adding a $1 clause
    my $field = $1;

    my ($op, $value);

    #figure out if it's a grouping
    my $keyword = $ARGS{ $field . "Field" } || $field;

    my ( @ops, @values );
    if ( ref $ARGS{ 'ValueOf' . $field } eq "ARRAY" ) {
        # we have many keys/values to iterate over, because there is
        # more than one CF with the same name.
        @ops    = @{ $ARGS{ $field . 'Op' } };
        @values = @{ $ARGS{ 'ValueOf' . $field } };
    }
    else {
        @ops    = ( $ARGS{ $field . 'Op' } );
        @values = ( $ARGS{ 'ValueOf' . $field } );
    }
    $RT::Logger->error("Bad Parameters passed into Query Builder")
        unless @ops == @values;

    for ( my $i = 0; $i < @ops; $i++ ) {
        my ( $op, $value ) = ( $ops[$i], $values[$i] );
        next if !defined $value || $value eq '';

        if ( $value eq 'NULL' && $op =~ /=/ ) {
            if ( $op eq '=' ) {
                $op = "IS";
            }
            elsif ( $op eq '!=' ) {
                $op = "IS NOT";
            }

            # This isn't "right", but...
            # It has to be this way until #5182 is fixed
            $value = "'NULL'";
        }
        else {
            $value =~ s/'/\\'/g;
            $value = "'$value'" unless $value =~ /^\d+$/;
        }

        my $clause = {
            Key   => $keyword,
            Op    => $op,
            Value => $value
        };

        push @new_values, RT::Interface::Web::QueryBuilder::Tree->new($clause);
    }
}

# }}}

push @actions, $m->comp('Elements/EditQuery:Process',
    %ARGS,
    Tree     => $tree,
    Selected => \@current_values,
    New      => \@new_values,
);

# {{{ Rebuild $Query based on the additions / movements

my $optionlist_arrayref;
($query{'Query'}, $optionlist_arrayref) = $tree->GetQueryAndOptionList(\@current_values);

my $optionlist = join "\n", map { qq(<option value="$_->{INDEX}" $_->{SELECTED}>) 
                                  . ("&nbsp;" x (5 * $_->{DEPTH}))
                                  . $m->interp->apply_escapes($_->{TEXT}, 'h') . qq(</option>) } @$optionlist_arrayref;

# }}}

my $queues = $tree->GetReferencedQueues;

# {{{ Deal with format changes
my ( $AvailableColumns, $CurrentFormat );
( $query{'Format'}, $AvailableColumns, $CurrentFormat ) = $m->comp(
    'Elements/BuildFormatString',
    %ARGS,
    cfqueues => $queues,
    Format => $query{'Format'},
);

# }}}

# if we're asked to save the current search, save it
push @actions, $m->comp( 'Elements/EditSearches:Save', %ARGS, Query => \%query, SavedSearch => \%saved_search);

# {{{ Push the updates into the session so we don't loose 'em

$session{'CurrentSearchHash'} = {
    %query,
    SearchId    => $saved_search{'Id'},
    Object      => $saved_search{'Object'},
    Description => $saved_search{'Description'},
};

# }}}

# {{{ Show the results, if we were asked.

if ( $ARGS{'DoSearch'} ) {
    $m->comp( 'Results.html', %query );
    $m->comp( '/Elements/Footer' );
    $m->abort;
}

# }}}

# {{{ Build a querystring for the tabs

my $QueryString = '';
if ($NewQuery) {
    $QueryString = 'NewQuery=1';
}
elsif ( $query{'Query'} ) {
    $QueryString = $m->comp('/Elements/QueryString', %query );
}

# }}}

</%INIT>

<%ARGS>
$NewQuery => 0
@clauses => ()
</%ARGS>
