import imp
import sys
import types
import pytest
from _pytest.python import PyCollector, Module

from ..dsl import APISuite
from .. import marks


"""pytest plugin for Ra.

Large amounts of code stolen from `pytest-describe
<http://github.com/ropez/pytest-describe>`_.
"""


def trace_function(funcobj, *args, **kwargs):
    """Call a function, and return its locals"""
    funclocals = {}

    def _tracefunc(frame, event, arg):
        # Activate local trace for first call only
        if frame.f_back.f_locals.get('_tracefunc') == _tracefunc:
            if event == 'return':
                funclocals.update(frame.f_locals.copy())

    sys.setprofile(_tracefunc)
    try:
        funcobj(*args, **kwargs)
    finally:
        sys.setprofile(None)

    return funclocals


def make_module_from_function(funcobj):
    """Evaluates the local scope of a function, as if it was a module"""
    module = imp.new_module(funcobj.__name__)
    scope = marks.get(funcobj, 'scope')
    funclocals = trace_function(funcobj, scope)
    module.__dict__.update(funclocals)
    return module


def copy_markinfo(module, funcobj):
    from _pytest.mark import MarkInfo

    marks = {}
    for name, val in funcobj.__dict__.items():
        if isinstance(val, MarkInfo):
            marks[name] = val

    for obj in module.__dict__.values():
        if isinstance(obj, types.FunctionType):
            for name, mark in marks.items():
                setattr(obj, name, mark)


def merge_pytestmark(module, parentobj):
    try:
        pytestmark = parentobj.pytestmark
        if not isinstance(pytestmark, list):
            pytestmark = [pytestmark]
        try:
            if isinstance(module.pytestmark, list):
                pytestmark.extend(module.pytestmark)
            else:
                pytestmark.append(module.pytestmark)
        except AttributeError:
            pass
        module.pytestmark = pytestmark
    except AttributeError:
        pass


class ResourceScopeCollector(PyCollector):
    def __init__(self, funcobj, parent):
        super(ResourceScopeCollector, self).__init__(funcobj.__name__, parent)
        self.funcobj = funcobj

    def collect(self):
        self.session._fixturemanager.parsefactories(self)
        return super(ResourceScopeCollector, self).collect()

    def _getobj(self):
        return self._memoizedcall('_obj', self._importtestmodule)

    def _makeid(self):
        return self.parent.nodeid + '::' + self.funcobj.__name__

    def _importtestmodule(self):
        """Import a ra test suite function scope as if it was a module"""
        module = make_module_from_function(self.funcobj)
        copy_markinfo(module, self.funcobj)
        merge_pytestmark(module, self.parent.obj)
        return module

    def funcnamefilter(self, name):
        """Treat all nested functions as tests, without requiring the 'test_'
        prefix, unless they begin with an underscore.

        Functions marked as pytest fixtures should already be ignored.
        """
        return not name.startswith('_')

    def classnamefilter(self, name):
        """Don't allow test classes"""
        return False

    def __repr__(self):
        return "<{} {}>".format(self.__class__.__name__,
                                repr(self.funcobj.__name__))


class AutotestCollector(PyCollector):
    def __init__(self, module, parent):
        super(AutotestCollector, self).__init__(module.__name__, parent)
        self._module = module

    def collect(self):
        self.session._fixturemanager.parsefactories(self)
        return super(AutotestCollector, self).collect()

    def _getobj(self):
        return self._module

    def _makeid(self):
        return self.parent.nodeid + '::' + self._module.__name__

    def funcnamefilter(self, name):
        """Treat all nested functions as tests, without requiring the 'test_'
        prefix, unless they begin with an underscore.

        Functions marked as pytest fixtures should already be ignored.
        """
        return not name.startswith('_')

    def classnamefilter(self, name):
        """Don't allow test classes"""
        return False

    def __repr__(self):
        return "<{} {}>".format(self.__class__.__name__,
                                repr(self._module.__name__))


def pytest_pycollect_makeitem(collector, name, obj):
    if isinstance(obj, types.FunctionType):
        if marks.get(obj, 'type')  == 'resource':
            return ResourceScopeCollector(obj, collector)
    elif isinstance(obj, marks.Mark):
        autotest = obj.get('autotest')
        if autotest:
            return AutotestCollector(autotest, collector)


@pytest.fixture
def req(request):
    return marks.get(request.function, 'req')


@pytest.fixture
def api(req):
    return req.scope.api


@pytest.fixture
def examples(api):
    return api.examples


@pytest.fixture
def app(api):
    return api.app

@@ -2149,16 +2149,483 @@
 uncobj%0A%0A
+    def _reorder_collected(self, data):%0A        %22%22%22 Move DELETE http method test to the end of all tests.%0A        %22%22%22%0A        priority = %7B%0A            'post':     1,%0A            'get':      2,%0A            'put':      2,%0A            'patch':    2,%0A            'head':     2,%0A            'options':  2,%0A            'delete':   3,%0A        %7D%0A        data = sorted(%0A            data,%0A            key=lambda x: priority.get(getattr(x, 'name', ''), 4))%0A        return data%0A%0A
     def 
@@ -2703,28 +2703,31 @@
 )%0A        re
-turn
+sults =
  super(Resou
@@ -2752,32 +2752,106 @@
  self).collect()
+%0A        results = self._reorder_collected(results)%0A        return results
 %0A%0A    def _getob
@@ -4005,32 +4005,367 @@
 odule = module%0A%0A
+    def _reorder_collected(self, data):%0A        %22%22%22 Move item route tests after collection route tests. %22%22%22%0A        data_dict = %7Bgetattr(res, 'name', ''): res for res in data%7D%0A        for name, res in data_dict.items():%0A            if '%7D' in name:%0A                data.remove(res)%0A                data.append(res)%0A        return data%0A%0A
     def collect(
@@ -4439,20 +4439,23 @@
       re
-turn
+sults =
  super(A
@@ -4487,16 +4487,90 @@
 ollect()
+%0A        results = self._reorder_collected(results)%0A        return results
 %0A%0A    de

Reorder collected tests