#   Copyright 2018 Braxton Mckee
#
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.

from nativepython.type_wrappers.refcounted_wrapper import RefcountedWrapper
from nativepython.typed_expression import TypedExpression
from nativepython.type_wrappers.exceptions import generateThrowException
import nativepython.type_wrappers.runtime_functions as runtime_functions

from typed_python import NoneType, Int64, String

import nativepython.native_ast as native_ast
import nativepython

from nativepython.native_ast import VoidPtr

typeWrapper = lambda t: nativepython.python_object_representation.typedPythonTypeToTypeWrapper(t)

class StringWrapper(RefcountedWrapper):
    is_pod = False
    is_empty = False
    is_pass_by_ref = True

    def __init__(self):
        super().__init__(String)

        self.layoutType = native_ast.Type.Struct(element_types=(
            ('refcount', native_ast.Int64),
            ('data', native_ast.UInt8)
            ), name='StringLayout').pointer()

    def getNativeLayoutType(self):
        return self.layoutType

    def on_refcount_zero(self, context, instance):
        assert instance.isReference
        return runtime_functions.free.call(instance.nonref_expr.cast(native_ast.UInt8Ptr))

    def convert_bin_op(self, context, left, op, right):
        if right.expr_type == left.expr_type:
            if op.matches.Eq or op.matches.NotEq or op.matches.Lt or op.matches.LtE or op.matches.GtE or op.matches.Gt:
                cmp_res = context.pushPod(
                    int,
                    runtime_functions.string_cmp.call(
                        left.nonref_expr.cast(VoidPtr),
                        right.nonref_expr.cast(VoidPtr)
                        )
                    )
                if op.matches.Eq:
                    return context.pushPod(bool,
                        cmp_res.nonref_expr.eq(0)
                        )
                if op.matches.NotEq:
                    return context.pushPod(bool,
                        cmp_res.nonref_expr.neq(0)
                        )
                if op.matches.Lt:
                    return context.pushPod(bool,
                        cmp_res.nonref_expr.lt(0)
                        )
                if op.matches.LtE:
                    return context.pushPod(bool,
                        cmp_res.nonref_expr.lte(0)
                        )
                if op.matches.Gt:
                    return context.pushPod(bool,
                        cmp_res.nonref_expr.gt(0)
                        )
                if op.matches.GtE:
                    return context.pushPod(bool,
                        cmp_res.nonref_expr.gte(0)
                        )

            if op.matches.Add:
                return context.push(str, lambda strRef:
                    strRef.expr.store(
                        runtime_functions.string_concat.call(
                            left.nonref_expr.cast(VoidPtr),
                            right.nonref_expr.cast(VoidPtr)
                            ).cast(self.layoutType)
                        )
                    )

        return super().convert_bin_op(context, left, op, right)

    def convert_getitem(self, context, expr, item):
        item = item.toInt64()

        len_expr = self.convert_len(context, expr)

        with context.ifelse((item.nonref_expr.lt(len_expr.nonref_expr.negate())).bitor(item.nonref_expr.gte(len_expr.nonref_expr))) as (true,false):
            with true:
                context.pushException(IndexError, "string index out of range")

        return context.push(str, lambda strRef:
            strRef.expr.store(
                runtime_functions.string_getitem_int64.call(expr.nonref_expr.cast(native_ast.VoidPtr), item.nonref_expr)
                    .cast(self.layoutType)
                )
            )

    def convert_len_native(self, expr):
        return native_ast.Expression.Branch(
                cond=expr,
                false=native_ast.const_int_expr(0),
                true=expr.ElementPtrIntegers(0,1).ElementPtrIntegers(4).cast(native_ast.Int32.pointer()).load().cast(native_ast.Int64)
                )

    def convert_len(self, context, expr):
        return context.pushPod(int, self.convert_len_native(expr.nonref_expr))

    def constant(self, context, s):
        return context.push(str, lambda strRef:
            strRef.expr.store(
                runtime_functions.string_from_utf8_and_len.call(
                    native_ast.const_utf8_cstr(s),
                    native_ast.const_int_expr(len(s))
                    ).cast(self.layoutType)
                )
            )



