# $Id: helpers.py 4dca5ad0f397 2010-03-10 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 pyke

def fc_head(rb_name):
    return (
        "# %s_fc.py" % rb_name,
        "",
        "from pyke import contexts, pattern, fc_rule, knowledge_base",
        "",
        "pyke_version = %r" % pyke.version,
        "compiler_version = %r" % pyke.compiler_version,
    )

def bc_head(rb_name):
    return (
        "# %s_bc.py" % rb_name,
        "",
        "from pyke import contexts, pattern, bc_rule",
        "",
        "pyke_version = %r" % pyke.version,
        "compiler_version = %r" % pyke.compiler_version,
    )

def plan_head(rb_name):
    return (
        "# %s_plans.py" % rb_name,
        "",
        "pyke_version = %r" % pyke.version,
        "compiler_version = %r" % pyke.compiler_version,
    )

def goal(rb_name, rule_name, goal_info, pred_plan_lines, python_lines):
    # returns plan_lines, goal_fn_head, goal_fn_tail, goal_decl_lines
    goal, goal_name, pattern_args, taking, start_lineno, end_lineno = goal_info
    assert goal == 'goal'
    goal_fn_head = (
        "",
        "def %s(rule, arg_patterns, arg_context):" % rule_name,
        ("INDENT", 2),
        "engine = rule.rule_base.engine",
        "patterns = rule.goal_arg_patterns()",
        "if len(arg_patterns) == len(patterns):",
        ("INDENT", 2),
        "context = contexts.bc_context(rule)",
        "try:",
        ("INDENT", 2),
        ("STARTING_LINENO", start_lineno),
        "if all(map(lambda pat, arg:",
        ("INDENT", 2),
        ("INDENT", 9),
        ("INDENT", 2),
        "pat.match_pattern(context, context,",
        ("INDENT", 18),
        "arg, arg_context),",
        "POPINDENT",
        "POPINDENT",
        "patterns,",
        "arg_patterns)):",
        ("ENDING_LINENO", end_lineno),
        "POPINDENT",
        "rule.rule_base.num_bc_rules_matched += 1",
    )
    goal_fn_tail = (
        "POPINDENT",
        "POPINDENT",
        "finally:",
        ("INDENT", 2),
        "context.done()",
        "POPINDENT",
        "POPINDENT",
        "POPINDENT",
    )
    if not taking and not pred_plan_lines and not python_lines:
        plan_fn_name = "None"
        plan_lines = ()
    else:
        plan_fn_name = "%s_plans.%s" % (rb_name, rule_name)
        def_start = "def %s" % rule_name
        taking = [line.strip() for line in taking if line.strip()]
        if not taking:
            def_head = def_start + '(context):'
        else:
            if taking[0][0] != '(' or taking[-1][-1] != ')':
                from pyke.krb_compiler import scanner
                end = scanner.lexer.lexpos
                taking_start = scanner.lexer.lexdata.rfind('taking', 0, end)
                if taking_start < 0:
                    raise SyntaxError("'taking' clause: missing parenthesis",
                                      scanner.syntaxerror_params())
                taking_start += len('taking')
                while taking_start < len(scanner.lexdata) and \
                      scanner.lexdata[taking_start].isspace():
                    taking_start += 1
                lineno = scanner.lexer.lineno - \
                         scanner.lexer.lexdata.count('\n', taking_start,
                                                     scanner.lexer.lexpos)
                raise SyntaxError("'taking' clause: missing parenthesis",
                                  scanner.syntaxerror_params(taking_start,
                                                             lineno))
            taking[0] = def_start + "(context, " + taking[0][1:]
            taking[-1] += ':'
            if len(taking) == 1:
                def_head = taking[0]
            else:
                def_head = (taking[0],
                            ('INDENT', 4),
                            tuple(taking[1:]),
                            "POPINDENT",
                           )
        plan_lines = ("",
                      def_head,
                      ('INDENT', 2),
                      pred_plan_lines,
                      python_lines,
                      "POPINDENT",
                     )
    goal_decl_lines = (
        "",
        "bc_rule.bc_rule(%r, This_rule_base, %r," % (rule_name, goal_name),
        ("INDENT", 16),
        "%s, %s," % (rule_name, plan_fn_name),
    ) + list_format(pattern_args, "(", "),")
    return plan_lines, goal_fn_head, goal_fn_tail, goal_decl_lines

def add_start(l, start):
    '''
        >>> add_start(('a', 'b', 'c'), '^')
        (0, ['^a', 'b', 'c'])
        >>> add_start(('POPINDENT', ('INDENT', 2), ((('b',), 'c'),),), '^')
        (2, ['POPINDENT', ('INDENT', 2), ((('^b',), 'c'),)])
        >>> add_start((('POPINDENT', ('INDENT', 2)), ((('b',), 'c'),),), '^')
        (1, [('POPINDENT', ('INDENT', 2)), ((('^b',), 'c'),)])
        >>> add_start(('POPINDENT', ('INDENT', 2)), '^')
        (0, ['^', 'POPINDENT', ('INDENT', 2)])
    '''
    ans = list(l)
    for first, x in enumerate(ans):
        if x != 'POPINDENT' and \
           not (isinstance(x, (tuple, list)) and x[0] == 'INDENT'):
            if not isinstance(x, (tuple, list)):
                ans[first] = start + ans[first]
                return first, ans
            f, x2 = add_start(x, start)
            if len(x) == len(x2):
                ans[first] = tuple(x2)
                return first, ans
    first = 0
    ans.insert(first, start)
    return first, ans

def add_end(l, end):
    '''
        >>> add_end(('a', 'b', 'c'), '^')
        (2, ['a', 'b', 'c^'])
        >>> add_end(((((('b',), 'c'),),), 'POPINDENT', ('INDENT', 2)), '^')
        (0, [(((('b',), 'c^'),),), 'POPINDENT', ('INDENT', 2)])
        >>> add_end((((('b',), 'c'),), ('POPINDENT', ('INDENT', 2))), '^')
        (0, [((('b',), 'c^'),), ('POPINDENT', ('INDENT', 2))])
        >>> add_end(('POPINDENT', ('INDENT', 2)), '^')
        (2, ['POPINDENT', ('INDENT', 2), '^'])
    '''
    ans = list(l)
    for last in range(len(ans) - 1, -1, -1):
        x = ans[last]
        if x != 'POPINDENT' and \
           not (isinstance(x, (tuple, list)) and x[0] == 'INDENT'):
            if not isinstance(x, (tuple, list)):
                ans[last] += end
                return last, ans
            e, x2 = add_end(x, end)
            if len(x) == len(x2):
                ans[last] = tuple(x2)
                return last, ans
    last = len(ans)
    ans.insert(last, end)
    return last, ans

def add_brackets(l, start = '(', end = ')'):
    '''
        >>> add_brackets(('a', 'b', 'c'))
        ('(a', ('INDENT', 1), 'b', 'c)', 'POPINDENT')
        >>> add_brackets(('(a', ('INDENT', 1), 'b', 'c)', 'POPINDENT'))
        ('((a', ('INDENT', 1), ('INDENT', 1), 'b', 'c))', 'POPINDENT', 'POPINDENT')
    '''
    if not l: return start + end
    first, ans = add_start(l, start)
    last, ans = add_end(ans, end)
    if last > first:
        ans.insert(last + 1, "POPINDENT")
        ans.insert(first + 1, ("INDENT", 1))
    return tuple(ans)

def list_format(l, start, end, separator = ','):
    ans = [element + separator for element in l]
    if not ans: return (start + end,)
    ans[0] = start + ans[0]
    ans[-1] += end
    if len(ans) > 1:
        ans.insert(1, ("INDENT", 1))
        ans.append("POPINDENT")
    return tuple(ans)

def merge_pattern(pattern, pattern_list):
    # returns pat_num, new_pattern_list
    if pattern in pattern_list:
        return list(pattern_list).index(pattern), pattern_list
    return len(pattern_list), pattern_list + (pattern,)

def merge_patterns(patterns, pattern_list):
    # returns pat_nums, new_pattern_list
    pat_nums = []
    for pat in patterns:
        pat_num, pattern_list = merge_pattern(pat, pattern_list)
        pat_nums.append(pat_num)
    return tuple(pat_nums), pattern_list

def syntax_error(msg, lineno, pos):
    raise SyntaxError(msg, scanner.syntaxerror_params(pos, lineno))

