# cython: language_level=3, annotation_typing=True, c_string_encoding=utf-8, boundscheck=False, wraparound=True, initializedcheck=False
# Aspidites
# Copyright (C) 2021 Ross J. Duff

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.

# This program 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, see <http://www.gnu.org/licenses/>.
from .._vendor.pyparsing import (
    IndentedBlock,
    Combine,
    Empty,
    FollowedBy,
    Forward,
    ZeroOrMore,
    OneOrMore,
    MatchFirst,
    Optional,
    ParseElementEnhance,
    ParseException,
    Regex,
    Char,
    Word,
    alphanums,
    alphas,
    col,
    conditionAsParseAction,
    delimitedList,
    infixNotation,
    matchOnlyAtCol,
    nums,
    opAssoc,
    quotedString,
    unicodeString,
)

from .._vendor.contracts.syntax import contract_expression
from .._vendor.contracts.library.suggester import create_suggester
from ..parser.convert import *

list_str = Forward().setName('list literal')
list_str_evolver = Forward().setName('list evolver literal')
list_index = Forward().setName('list index operation')
list_set = Forward().setName('list set operation')
list_count = Forward().setName('list count operation')
list_append = Forward().setName('list append operation')
list_remove = Forward().setName('list remove operation')
dict_items = Forward().setName('dictionary items operation')
dict_keys = Forward().setName('dictionary keys operation')
dict_vals = Forward().setName('dictionary values operation')
dict_discard = Forward().setName('dictionary discard operation')
dict_update = Forward().setName('dictionary update operation')
dict_copy = Forward().setName('dictionary copy operation')
dict_remove = Forward().setName('dictionary remove operation')
set_discard = Forward().setName('set discard operation')
set_update = Forward().setName('set update operation')
set_copy = Forward().setName('set copy operation')
set_remove = Forward().setName('set remove operation')
set_str = Forward().setName('set literal')
set_str_evolver = Forward().setName('set evolver literal')
dict_str = Forward().setName('dictionary literal')
dict_str_evolver = Forward().setName('dictionary evolver literal')
# tuple_str = Forward()
slice_str = Forward().setName('list slice operation')
simple_assign = Forward().setName('simple assignment')
slice_assign = Forward().setName('slice assignment')
func_call = Forward().setName('function call')
suite = Forward().setName('indented block')
break_if = Forward().setName('conditional break')
cont_if = Forward().setName('conditional continue')
if_slice = Forward().setName('conditional slice')
if_simple = Forward().setName('conditional assignment')
if_func_call = Forward().setName('conditional function call')
if_return = Forward().setName('conditional return')
rvalue = Forward().setName('return value')
stmt = Forward().setName('statement')

quoted_str = quotedString().setParseAction(lambda s, l, t: t[0])
quoted_str.setName('string literal')
integer = Word(nums).setParseAction(cvt_int)
integer.setName('integer numeric literal')
real = Combine(Optional(Word(nums)) + "." + Word(nums))
real.setName('floating point numeric literal')
complex_ = Combine(real | integer + "+" + real | integer + "j")
complex_.setName('complex numeric literal')
identifier = Word(alphas + "_", alphanums + "_") + Optional(persist)
identifier.setName('identifier')
operand = nullit | complex_ | real | bool_literal | integer | identifier | underscore
operand.setName('operand')
arith_expr = Combine(
    infixNotation(
        operand,
        [
            (factop, 1, opAssoc.LEFT),
            (exponp, 2, opAssoc.RIGHT),
            (signop, 1, opAssoc.RIGHT),  # Changing this to LEFT causes the entire parser to break
            (multop, 2, opAssoc.LEFT),
            (plusop, 2, opAssoc.LEFT),
            (bitwiseop, 2, opAssoc.LEFT),
        ],
        lpar=lit_lparen,
        rpar=lit_rparen,
    )
).setParseAction(cvt_arith_expr)
arith_expr.setName('arithmetic expression')
comp_expr = infixNotation(
    arith_expr,
    [
        (comparisonop, 2, opAssoc.LEFT),
    ],
).setParseAction(lambda s, l, t: ''.join(t[0]))
comp_expr.setName('comparison expression')
# TODO dict trigrams cause RecursionError:
#  maximum recursion depth exceeded while getting the str of an object
list_item = (  # Precedence important!!!
        slice_str
        | comp_expr  # Expressions
        | arith_expr
        | identifier
        | integer  # Literals
        | complex_
        | real
        | bool_literal
        | nullit
        | quoted_str
        | list_str_evolver
        | set_str_evolver
        | dict_str_evolver
        | list_str
        | set_str
        | dict_str
).setName('list item')
collection_trigrams = (list_set
                       | list_index
                       | list_count
                       | dict_discard
                       | set_discard
                       | list_append
                       | dict_copy
                       | set_copy
                       | list_remove
                       | dict_remove
                       | set_remove).setName('collections trigram')
list_index <<= (
        (identifier | list_str | slice_str) + index_items.setParseAction(
    replaceWith('.index')) + list_item).setParseAction(
    cvt_list_index)

list_count <<= (
        (identifier | list_str | slice_str) + count_vals.setParseAction(
    replaceWith('.count')) + list_item).setParseAction(
    cvt_list_index)

list_set <<= (
        identifier + set_add.setParseAction(
    replaceWith('.set')) + identifier + ',' + identifier).setParseAction(
    cvt_list_index)

list_append <<= (
        (identifier | list_str | slice_str) + append.setParseAction(
    replaceWith('.append')) + list_item).setParseAction(
    cvt_list_index)

list_remove <<= (
        (identifier | list_str | slice_str) + remove.setParseAction(
    replaceWith('.remove')) + list_item).setParseAction(
    cvt_list_index)

# dict_items <<= ((dict_str) + index_items.setParseAction(replaceWith('.items()')))
# dict_keys <<= ((dict_str) + keys.setParseAction(replaceWith('.keys()')))
# dict_vals <<= ((dict_str) + count_vals.setParseAction(replaceWith('.values()')))
dict_discard <<= (
        (dict_str) + discard.setParseAction(
    replaceWith('.discard')) + list_item).setParseAction(
    cvt_list_index)
dict_update <<= (
        (identifier | dict_str) + update.setParseAction(
    replaceWith('.update')) + list_item).setParseAction(
    cvt_list_index)

dict_copy <<= ((identifier | dict_str) + copy.setParseAction(replaceWith('.copy()')))

dict_remove <<= (
        (identifier | dict_str) + remove.setParseAction(
    replaceWith('.remove')) + list_item).setParseAction(
    cvt_list_index)

# set_discard <<= (
#         (identifier | set_str) + discard.setParseAction(
#     replaceWith('.discard')) + list_item).setParseAction(
#     cvt_list_index)
# set_update <<= (
#         (identifier | set_str) + append.setParseAction(
#     replaceWith('.append')) + list_item).setParseAction(
#     cvt_list_index)

set_copy <<= ((identifier | set_str) + copy.setParseAction(replaceWith('.copy()')))

set_remove <<= (
        (identifier | set_str) + remove.setParseAction(
    replaceWith('.remove')) + list_item).setParseAction(
    cvt_list_index)

lit_ellipse = Literal("...").setParseAction(replaceWith('...'))
assignable = collection_trigrams | func_call | list_item | lit_ellipse

_contract_expression = contract_expression.copy()
_contract_expression.setParseAction(lambda s, l, tks: f"'{''.join((str(t) for t in tks))}'")
contract_define = identifier + imposes + _contract_expression
contract_define.setParseAction(cvt_contract_define)
contract_respect = respects + _contract_expression
contract_assign = identifier + assign_eq + assignable + contract_respect
contract_assign.setParseAction(cvt_contract_assign)

# tuple_str <<= (lparen + Optional(delimitedList(list_item)) + Optional(comma) + rparen).setParseAction(cvt_tuple)
noclosure.setParseAction(replaceWith('.evolver()'))
list_str <<= (lbrack + Optional(delimitedList(list_item) + Optional(comma)) + rbrack).setParseAction(cvt_list)
list_str_evolver <<= ((lbrack + Optional(delimitedList(list_item) + Optional(comma)) + rbrack).setParseAction(
    cvt_list) + noclosure).setParseAction(lambda s, l, t: ''.join(t))
set_str <<= (lbrace + Optional(delimitedList(list_item) + Optional(comma)) + rbrace).setParseAction(cvt_set)
set_str_evolver <<= ((lbrace + Optional(delimitedList(list_item) + Optional(comma)) + rbrace).setParseAction(
    cvt_set) + noclosure).setParseAction(lambda s, l, t: ''.join(t))
dict_entry = Group(list_item + colon + list_item)
dict_str <<= (lbrace + Optional(delimitedList(dict_entry) + Optional(comma)) + rbrace).setParseAction(cvt_dict)
dict_str_evolver <<= ((lbrace + Optional(delimitedList(dict_entry) + Optional(comma)) + rbrace).setParseAction(
    cvt_dict) + noclosure).setParseAction(lambda s, l, t: ''.join(t))
slice_str <<= identifier + lit_lbrack + Optional(integer | identifier) + \
              Optional(lit_colon) + Optional(integer | identifier) +\
              Optional(lit_colon) + Optional(integer | identifier) + lit_rbrack
slice_str.setParseAction(lambda s, l, t: ''.join(str(i) for i in t))
def_args = Optional(delimitedList(contract_assign, delim=";")).setParseAction(lambda s, l, t: sep.join(t))
def_args = Group(lit_lparen + def_args + args_end).setParseAction(lambda s, l, t: "".join(*t))

bool_pragmas = Combine(
    pragma + oneOf(' '.join(available_bool_pragmas)) + lit_lparen + oneOf('True False') + lit_rparen
).setParseAction(cvt_pragma)

pragmas = Combine(pragma + oneOf(' '.join(available_pragmas))).setParseAction(cvt_pragma)

func_decl = Group(
    Optional(OneOrMore(bool_pragmas) | OneOrMore(pragmas)) +
    private_def_decl + identifier + def_args + _contract_expression).setParseAction(
    lambda s, l, t: "\n@__contract()\n" + "".join(*t) + lit_colon)
func_decl.setName('function declaration')
comment_line = (Combine(Regex(r"`(?:[^`\n\r\\]|(?:``)|(?:\\(?:[^x]|x[0-9a-fA-F]+)))*") + "`").setParseAction(
    lambda s, l, t: t[0]).setParseAction(cvt_comment_line))

return_value = return_none + rvalue
yield_value = yield_none + rvalue
ret_stmt = Group(return_value).setParseAction(lambda s, l, t: "".join(*t))
yield_stmt = Group(yield_value).setParseAction(lambda s, l, t: "".join(*t))

func_def = Group(func_decl + suite).setParseAction(lambda s, l, t: nl_indent.join(t[0]) + "\n")
lambda_def = Combine(Group(lit_lparen + arith_expr | comp_expr + lit_rparen))
func_call <<= Group(identifier + lit_lparen + Optional(delimitedList(rvalue)) + lit_rparen).setParseAction(
    cvt_func_call)  # if len(t[0]) != 3 else t[0][0] + '()')
clos_call = Group(identifier + lit_lparen + Optional(delimitedList(rvalue)) + lit_rparen).setParseAction(
    cvt_clos_call) + Suppress(noclosure)
context_suite = Forward()
context_def = Group(context_stmt + func_call).setParseAction(lambda s, l, t: ''.join(*t) + lit_colon)

context_decl = Group(context_def + context_suite).setParseAction(
    lambda s, l, t: ''.join(*t))

# TODO (!): trigram only binds a single letter variable identifier
case_stmt = Group(assignable + colon + func_call).setParseAction(lambda s, l, t: sep.join(t[0]))
match_suite = Group(IndentedBlock(OneOrMore(case_stmt))).setParseAction(lambda s, l, t: (sep.join(t.asList()[0])))
match_decl = Group(match_none + identifier).setParseAction(lambda s, l, t: t[0][1] + '=' + t[0][0] + lit_lparen + t[0][1])
match_def = Group(match_decl + match_suite).setParseAction(lambda s, l, t: sep.join(t[0]) + lit_rparen)

loop_suite = Forward()

ident_loop_decl = Group(identifier + Optional(lit_comma + identifier) + for_none + identifier).setParseAction(
    cvt_for_loop_decl)
ident_loop_def = Group(ident_loop_decl + loop_suite).setParseAction(lambda s, l, t: (nl_indent + indent).join(t[0]))

list_loop_decl = Group(identifier + Optional(lit_comma + identifier) + for_none + list_str).setParseAction(
    cvt_for_loop_decl)
list_loop_def = Group(list_loop_decl + loop_suite).setParseAction(lambda s, l, t: (nl_indent + indent).join(t[0]))

set_loop_decl = Group(identifier + Optional(lit_comma + identifier) + for_none + set_str).setParseAction(
    cvt_for_loop_decl)
set_loop_def = Group(set_loop_decl + loop_suite).setParseAction(lambda s, l, t: (nl_indent + indent).join(t[0]))

# tuple_loop_decl = Group(identifier + Optional(lit_comma + identifier) + for_none + tuple_str).setParseAction(
#     cvt_for_loop_decl)
# tuple_loop_def = Group(tuple_loop_decl + loop_suite).setParseAction(lambda s, l, t: (nl_indent + indent).join(t[0]))

dict_loop_decl = Group(identifier + Optional(lit_comma + identifier) + for_none + dict_str).setParseAction(
    cvt_for_loop_decl)
dict_loop_def = Group(dict_loop_decl + loop_suite).setParseAction(lambda s, l, t: (nl_indent + indent).join(t[0]))

string_loop_decl = Group(identifier + Optional(lit_comma + identifier) + for_none + quoted_str).setParseAction(
    cvt_for_loop_decl)
string_loop_def = Group(string_loop_decl + loop_suite).setParseAction(lambda s, l, t: (nl_indent + indent).join(t[0]))

func_loop_decl = Group(identifier + Optional(lit_comma + identifier) + for_none + func_call).setParseAction(
    cvt_for_loop_decl)
func_loop_def = Group(func_loop_decl + loop_suite).setParseAction(lambda s, l, t: (nl_indent + indent).join(t[0]))
break_if <<= (break_stmt + rvalue).setParseAction(lambda s, l, t: 'if ' + t[1] + lit_colon + t[0])
cont_if <<= (cont_stmt + rvalue).setParseAction(lambda s, l, t: 'if ' + t[1] + lit_colon + t[0])
if_slice <<= (rvalue + if_cond + slice_assign).setParseAction(lambda s, l, t: t[1] + t[0] + lit_colon + t[2])
if_simple <<= (rvalue + if_cond + simple_assign).setParseAction(lambda s, l, t: t[1] + t[0] + lit_colon + t[2])
if_func_call <<= (rvalue + if_cond + func_call).setParseAction(lambda s, l, t: t[1] + t[0] + lit_colon + t[2])
if_return <<= (rvalue + if_cond + return_value | return_none).setParseAction(lambda s, l, t: t[1] + t[0] + lit_colon + t[2] + t[3])

loop_suite <<= IndentedBlock(
    OneOrMore(pass_stmt
              | yield_stmt
              | cont_if
              | cont_stmt
              | break_if
              | break_stmt
              | if_slice
              | if_simple
              | if_func_call
              | if_return
              | func_call
              | simple_assign
              | slice_assign
              ))
# TODO context managers get eaten by the preceding code blocks
context_suite <<= IndentedBlock(OneOrMore(contract_assign | match_def | func_call)).setParseAction(
    lambda s, l, t: (nl_indent.join(t.asList())))

suite <<= IndentedBlock(
    OneOrMore(pass_stmt
              | ret_stmt
              | yield_stmt
              | ident_loop_def
              | string_loop_def
              | list_loop_def
              | set_loop_def
              # | tuple_loop_def
              | dict_loop_def
              | func_loop_def
              | match_def
              | if_slice
              | if_simple
              | if_func_call
              | if_return
              | slice_assign
              | contract_assign)).setParseAction(
    lambda s, l, t: (nl_indent.join(t.asList())))
rvalue <<= collection_trigrams | clos_call | func_call | list_item | lambda_def
simple_assign << Group(identifier + assign_eq + rvalue).setParseAction(lambda s, l, t: " ".join(t[0]))
slice_assign << Group(slice_str + assign_eq + rvalue).setParseAction(lambda s, l, t: " ".join(t[0]))
stmt <<= (func_def | contract_define | simple_assign)
module_body = OneOrMore(stmt) + Optional(
    struct_main + OneOrMore(stmt).setParseAction(lambda s, l, t: indent + nl_indent.join(t)))
module_body.ignore(comment_line)
module_body.setName('module')


def parse_module(module):
    return module_body.parseString(module, parseAll=True)


def parse_statement(s):
    return stmt.parseString(s)

# if __name__ == "__main__":
#     print(parse_module(open('../examples/math.wom').read()))
