##############################################################################
#
#  This library is free software; you can redistribute it and/or
#  modify it under the terms of the GNU Library General Public
#  License as published by the Free Software Foundation; either
#  version 2 of the License, or (at your option) any later version.
#
#  This library 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
#  Library General Public License for more details.
#
#  You should have received a copy of the GNU Library General Public
#  License along with this library; if not, write to the
#  Free Software Foundation, Inc., 59 Temple Place - Suite 330,
#  Boston, MA  02111-1307, USA.
#
#  Jabber
#  Copyright (C) 1998-2004 Jabber Software Foundation http://jabber.org/
#
##############################################################################

package XML::Stream::XPath::Query;

use 5.008;
use strict;
use Carp;
use vars qw( $VERSION );

$VERSION = "1.23";

sub new
{
    my $proto = shift;
    my $self = { };

    bless($self,$proto);

    $self->{TOKENS} = [ '/','[',']','@','"',"'",'=','!','(',')',':',' ',',']; 
    $self->{QUERY} = shift;
    
    if (!defined($self->{QUERY}) || ($self->{QUERY} eq ""))
    {
        confess("No query string specified");
    }
    
    $self->parseQuery();
    
    return $self;
}


sub getNextToken
{ 
    my $self = shift;
    my $pos = shift;

    my @toks = grep{ $_ eq substr($self->{QUERY},$$pos,1)} @{$self->{TOKENS}};
    while( $#toks == -1 )
    {
        $$pos++;
        if ($$pos > length($self->{QUERY}))
        {
            $$pos = length($self->{QUERY});
            return 0;
        }
        @toks = grep{ $_ eq substr($self->{QUERY},$$pos,1)} @{$self->{TOKENS}};
    }

    return $toks[0];
}


sub getNextIdentifier
{
    my $self = shift;
    my $pos = shift;
    my $sp = $$pos;
    $self->getNextToken($pos);
    return substr($self->{QUERY},$sp,$$pos-$sp);
}


sub getOp
{
    my $self = shift;
    my $pos = shift;
    my $in_context = shift;
    $in_context = 0 unless defined($in_context);

    my $ret_op;

    my $loop = 1;
    while( $loop )
    {
        my $pos_start = $$pos;

        my $token = $self->getNextToken($pos);
        if (($token eq "0") && $in_context)
        {
            return;
        }

        my $token_start = ++$$pos;
        my $ident;
     
        if (defined($token))
        {

            if ($pos_start != ($token_start-1))
            {
                $$pos = $pos_start;
                my $temp_ident = $self->getNextIdentifier($pos);
                $ret_op = new XML::Stream::XPath::NodeOp($temp_ident,"0");
            }
            elsif ($token eq "/")
            {
                if (substr($self->{QUERY},$token_start,1) eq "/")
                {
                    $$pos++;
                    my $temp_ident = $self->getNextIdentifier($pos);
                    $ret_op = new XML::Stream::XPath::AllOp($temp_ident);
                }
                else
                {
                    my $temp_ident = $self->getNextIdentifier($pos);
                    if ($temp_ident ne "")
                    {
                        $ret_op = new XML::Stream::XPath::NodeOp($temp_ident,($pos_start == 0 ? "1" : "0"));
                    }
                }
            }
            elsif ($token eq "\@")
            {
                $ret_op = new XML::Stream::XPath::AttributeOp($self->getNextIdentifier($pos));
            }
            elsif ($token eq "]")
            {
                if ($in_context eq "[")
                {
                    $ret_op = pop(@{$self->{OPS}});
                    $in_context = 0;
                }
                else
                {
                    confess("Found ']' but not in context");
                    return;
                }
            }
            elsif (($token eq "\"") || ($token eq "\'"))
            {
                $$pos = index($self->{QUERY},$token,$token_start);
                $ret_op = new XML::Stream::XPath::Op("LITERAL",substr($self->{QUERY},$token_start,$$pos-$token_start));
                $$pos++;
            }
            elsif ($token eq " ")
            {
                $ident = $self->getNextIdentifier($pos);
                if ($ident eq "and")
                {
                    $$pos++;
                    my $tmp_op = $self->getOp($pos,$in_context);
                    if (!defined($tmp_op))
                    {
                        confess("Invalid 'and' operation");
                        return;
                    }
                    $ret_op = new XML::Stream::XPath::AndOp($self->{OPS}->[$#{$self->{OPS}}],$tmp_op);
                    $in_context = 0;
                    pop(@{$self->{OPS}});
                }
                elsif ($ident eq "or")
                {
                    $$pos++;
                    my $tmp_op = $self->getOp($pos,$in_context);
                    if (!defined($tmp_op))
                    {
                        confess("Invalid 'or' operation");
                        return;
                    }
                    $ret_op = new XML::Stream::XPath::OrOp($self->{OPS}->[$#{$self->{OPS}}],$tmp_op);
                    $in_context = 0;
                    pop(@{$self->{OPS}});
                }
            }
            elsif ($token eq "[")
            {
                if ($self->getNextToken($pos) eq "]")
                {
                    if ($$pos == $token_start)
                    {
                        confess("Nothing in the []");
                        return;
                    }
                    
                    $$pos = $token_start;
                    my $val = $self->getNextIdentifier($pos);
                    if ($val =~ /^\d+$/)
                    {
                        $ret_op = new XML::Stream::XPath::PositionOp($val);
                        $$pos++;
                    }
                    else
                    {
                        $$pos = $pos_start + 1;
                        $ret_op = new XML::Stream::XPath::ContextOp($self->getOp($pos,$token));
                    }
                }
                else
                {
                    $$pos = $pos_start + 1;
                    $ret_op = new XML::Stream::XPath::ContextOp($self->getOp($pos,$token));
                }
            }
            elsif ($token eq "(")
            {
                #-------------------------------------------------------------
                # The function name would have been mistaken for a NodeOp.
                # Pop it off the back and get the function name.
                #-------------------------------------------------------------
                my $op = pop(@{$self->{OPS}});
                if ($op->getType() ne "NODE")
                {
                    confess("No function name specified.");
                }
                my $function = $op->getValue();
                if (!exists($XML::Stream::XPath::FUNCTIONS{$function}))
                {
                    confess("Undefined function \"$function\"");
                }
                $ret_op = new XML::Stream::XPath::FunctionOp($function);

                my $op_pos = $#{$self->{OPS}} + 1;

                $self->getOp($pos,$token);
                
                foreach my $arg ($op_pos..$#{$self->{OPS}})
                {
                    $ret_op->addArg($self->{OPS}->[$arg]);
                }

                splice(@{$self->{OPS}},$op_pos);
                
            }
            elsif ($token eq ")")
            {
                if ($in_context eq "(")
                {
                    $ret_op = undef;
                    $in_context = 0;
                }
                else
                {
                    confess("Found ')' but not in context");
                }
            }
            elsif ($token eq ",")
            {
                if ($in_context ne "(")
                {
                    confess("Found ',' but not in a function");
                }
  
            }
            elsif ($token eq "=")
            {
                my $tmp_op;
                while(!defined($tmp_op))
                {
                    $tmp_op = $self->getOp($pos);
                }
                $ret_op = new XML::Stream::XPath::EqualOp($self->{OPS}->[$#{$self->{OPS}}],$tmp_op);
                pop(@{$self->{OPS}});
            }
            elsif ($token eq "!")
            {
                if (substr($self->{QUERY},$token_start,1) ne "=")
                {
                    confess("Badly formed !=");
                }
                $$pos++;
                
                my $tmp_op;
                while(!defined($tmp_op))
                {
                    $tmp_op = $self->getOp($pos);
                }
                $ret_op = new XML::Stream::XPath::NotEqualOp($self->{OPS}->[$#{$self->{OPS}}],$tmp_op);
                pop(@{$self->{OPS}});
            }
            else
            {
                confess("Unhandled \"$token\"");
            }

            if ($in_context)
            {
                if (defined($ret_op))
                {
                    push(@{$self->{OPS}},$ret_op);
                }
                $ret_op = undef;
            }
        }
        else
        {
            confess("Token undefined");
        }
        
        $loop = 0 unless $in_context;
    }

    return $ret_op;
}


sub parseQuery
{
    my $self = shift;
    my $query = shift;

    my $op;
    my $pos = 0;
    while($pos < length($self->{QUERY}))
    {
        $op = $self->getOp(\$pos);
        if (defined($op))
        {
            push(@{$self->{OPS}},$op);
        }
    }

    #foreach my $op (@{$self->{OPS}})
    #{
    #    $op->display();
    #}

    return 1;
}


sub execute
{
    my $self = shift;
    my $root = shift;

    my $ctxt = new XML::Stream::XPath::Value($root);

    foreach my $op (@{$self->{OPS}})
    {
        if (!$op->isValid(\$ctxt))
        {
            $ctxt->setValid(0);
            return $ctxt;
        }
    }

    $ctxt->setValid(1);
    return $ctxt;
}


sub check
{
    my $self = shift;
    my $root = shift;

    my $ctxt = $self->execute($root);
    return $ctxt->check();
}


1;

