# $Id: pattern.py 081917d30609 2010-03-05 mtnyogi $
# coding=utf-8
# 
# Copyright © 2007-2008 Bruce Frederiksen
# 
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# 
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

import types
import itertools

class pattern(object):
    def __ne__(self, b): return not (self == b)

    def simple_match_pattern(self, bindings, my_context, pattern_b, b_context):
        return self.match_pattern(bindings, my_context, pattern_b, b_context)

    def lookup(self, context, allow_variable_in_ans = False):
        return self

class pattern_literal(pattern):
    def __init__(self, literal):
        self.literal = literal

    def __hash__(self): return hash(self.literal)

    def __eq__(self, b):
        if isinstance(b, pattern_literal): return self.literal == b.literal
        return self.literal == b

    def match_data(self, bindings, my_context, data):
        return self.literal == data

    def match_pattern(self, bindings, my_context, pattern_b, b_context):
        if isinstance(pattern_b, pattern_literal):
            return self.literal == pattern_b.literal
        return pattern_b.match_data(bindings, b_context, self.literal)

    def as_data(self, my_context, allow_vars = False, final = None):
        return self.literal

    def is_data(self, my_context):
        return True

class pattern_tuple(pattern):
    def __init__(self, elements, rest_var = None):
        self.elements = tuple(elements)
        self.rest_var = rest_var

    def __hash__(self):
        return hash(self.elements) ^ hash(self.rest_var)

    def __eq__(self, b):
        return isinstance(b, pattern_tuple) and \
               self.elements == b.elements and self.rest_var == b.rest_var

    def match_data(self, bindings, my_context, data):
        if isinstance(data, types.StringTypes): return False
        try:
            data = tuple(data)
        except TypeError:
            return False
        if len(self.elements) > len(data) or \
           self.rest_var is None and len(self.elements) < len(data):
            return False
        for x, y in itertools.izip(self.elements, data):
            if not x.match_data(bindings, my_context, y): return False
        if self.rest_var is not None:
            return self.rest_var.match_data(bindings, my_context,
                                            tuple(data[len(self.elements):]))
        return True

    def simple_match_pattern(self, bindings, my_context, pattern_b, b_context):
        return self, my_context

    def match_pattern(self, bindings, my_context, pattern_b, b_context):
        simple_ans = pattern_b.simple_match_pattern(bindings, b_context,
                                                    self, my_context)
        if isinstance(simple_ans, bool): return simple_ans
        pattern_b, b_context = simple_ans
        if not isinstance(pattern_b, pattern):
            return self.match_data(bindings, my_context, pattern_b)
        assert isinstance(pattern_b, pattern_tuple), "Internal logic error"

        my_len = len(self.elements)
        b_len = len(pattern_b.elements)
        if pattern_b.rest_var is None and my_len > b_len or \
           self.rest_var is None and my_len < b_len:
            return False
        for x, y in itertools.izip(self.elements, pattern_b.elements):
            if not x.match_pattern(bindings, my_context, y, b_context):
                return False
        if my_len <= b_len and self.rest_var is not None:
            # This is where the two rest_vars are bound together if my_len ==
            # b_len.
            tail_val, tail_context = pattern_b._tail(my_len, b_context)
            if tail_context is None:
                if not self.rest_var.match_data(bindings, my_context, tail_val):
                    return False
            else:
                if not self.rest_var.match_pattern(bindings, my_context,
                                                   tail_val, tail_context):
                    return False
        elif pattern_b.rest_var is not None:
            tail_val, tail_context = self._tail(b_len, my_context)
            if tail_context is None:
                if not pattern_b.rest_var.match_data(bindings, b_context,
                                                     tail_val):
                    return False
            else:
                if not pattern_b.rest_var.match_pattern(bindings, b_context,
                                                        tail_val, tail_context):
                    return False
        return True

    def as_data(self, my_context, allow_vars = False, final = None):
        ans = tuple(x.as_data(my_context, allow_vars, final)
                    for x in self.elements)
        if self.rest_var is None:
            return ans
        rest = my_context.lookup_data(self.rest_var.name, allow_vars, final)
        if isinstance(rest, tuple): return ans + rest
        return ans + ('*' + rest,)

    def _tail(self, n, my_context):
        """ Return a copy of myself with the first n elements removed.
        """
        if n == len(self.elements):
            if self.rest_var is None: return (), None
            return self.rest_var, my_context
        rest_elements = self.elements[n:]
        if self.rest_var is None and \
           all(isinstance(x, pattern_literal) for x in rest_elements):
            return tuple(x.literal for x in rest_elements), None
        return pattern_tuple(self.elements[n:], self.rest_var), my_context

    def is_data(self, my_context):
        arg_test = all(arg_pat.is_data(my_context) for arg_pat in self.elements)
        if not arg_test or self.rest_var is None: return arg_test
        return self.rest_var.is_data(my_context)

