#    Copyright (c) 2014 Mirantis, Inc.
#
#    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.

import inspect
import os.path

from yaql import specs

from murano.dsl import constants
from murano.dsl import dsl
from murano.dsl import dsl_types
from murano.dsl import helpers
from murano.dsl import yaql_integration


@dsl.name('io.murano.StackTrace')
class StackTrace(object):
    def __init__(self, this, context, include_native_frames=True):
        frames = []
        caller_context = context
        while True:
            if not caller_context:
                break
            frame = compose_stack_frame(caller_context)
            frames.append(frame)
            caller_context = helpers.get_caller_context(caller_context)
        frames.reverse()
        frames.pop()

        if include_native_frames:
            native_frames = []
            for frame in inspect.trace()[1:]:
                location = dsl_types.ExpressionFilePosition(
                    os.path.abspath(frame[1]), frame[2],
                    -1, frame[2], -1)
                method = frame[3]
                native_frames.append({
                    'instruction': frame[4][0].strip(),
                    'location': location,
                    'method': method,
                    'class': None
                })
            frames.extend(native_frames)

        this.data().frames = frames

    @specs.meta(constants.META_NO_TRACE, True)
    @specs.meta('Usage', 'Action')
    def to_string(self, this, prefix=''):
        return '\n'.join([format_frame(t, prefix)for t in this['frames']])


def compose_stack_frame(context):
    instruction = helpers.get_current_instruction(context)
    method = helpers.get_current_method(context)
    return {
        'instruction': None if instruction is None
        else str(instruction),

        'location': None if instruction is None
        else instruction.source_file_position,

        'method': None if method is None else method.name,
        'class': None if method is None else method.murano_class.name
    }


def format_frame(frame, prefix=''):
    instruction = frame['instruction']
    method = frame['method']
    murano_class = frame['class']
    location = frame['location']
    if murano_class:
        method += ' of class ' + murano_class

    if location:
        args = (
            os.path.abspath(location.file_path),
            location.start_line,
            ':' + str(location.start_column)
            if location.start_column >= 0 else '',
            method,
            instruction,
            prefix
        )
        return ('{5}File "{0}", line {1}{2} in method {3}\n'
                '{5}    {4}').format(*args)
    else:
        return '{2}File <unknown> in method {0}\n{2}    {1}'.format(
            method, instruction, prefix)


def create_stack_trace(context, include_native_frames=True):
        stacktrace = yaql_integration.call_func(
            context, 'new', 'io.murano.StackTrace',
            includeNativeFrames=include_native_frames)
        executor = helpers.get_executor(context)
        return dsl.MuranoObjectInterface(
            stacktrace, executor)
