# coding=utf-8

import sys
import traceback
import unittest
import warnings

from unittest.util import safe_repr

from pocounit.fixture import FixtureUnit
from pocounit.result import PocoTestResult
from pocounit.result.metainfo import MetaInfo
from pocounit.result.collector import PocoResultCollector
from pocounit.result.trace import ScriptTracer
from pocounit.result.record import ScreenRecorder
from pocounit.result.action import ActionRecorder
from pocounit.result.app_runtime import AppRuntimeLog
from pocounit.result.runner_runtime import RunnerRuntimeLog
from pocounit.result.assertion import AssertionRecorder


class PocoTestCase(unittest.TestCase, FixtureUnit):
    """
    * longMessage: determines whether long messages (including repr of
        objects used in assert methods) will be printed on failure in *addition*
        to any explicit message passed.
    """
    longMessage = True

    _resule_collector = None
    _result_emitters = None     # {name -> PocoTestResultEmitter}
    _addons = None              # [addons]

    def __init__(self):
        unittest.TestCase.__init__(self)
        FixtureUnit.__init__(self)

        collector = PocoResultCollector(self.project_root, [self.test_case_filename], self.name(), self.test_case_dir)
        self.set_result_collector(collector)

        meta_info_emitter = MetaInfo(collector)
        runner_runtime_log = RunnerRuntimeLog(collector)
        tracer = ScriptTracer(collector)
        screen_recorder = ScreenRecorder(collector)
        action_recorder = ActionRecorder(collector)
        app_runtime_log = AppRuntimeLog(collector)
        assertion_recorder = AssertionRecorder(collector)

        self.add_result_emitter('metaInfo', meta_info_emitter)
        self.add_result_emitter('runnerRuntimeLog', runner_runtime_log)
        self.add_result_emitter('tracer', tracer)
        self.add_result_emitter('screenRecorder', screen_recorder)
        self.add_result_emitter('actionRecorder', action_recorder)
        self.add_result_emitter('appRuntimeLog', app_runtime_log)
        self.add_result_emitter('assertionRecorder', assertion_recorder)

        self.meta_info_emitter = meta_info_emitter

    @classmethod
    def name(cls):
        """
        改写此方法来自定义testcase的名字，允许中文，请使用utf-8编码的str或者unicode

        :return:
        """

        if type(cls) is type:
            return cls.__name__
        else:
            return cls.__class__.__name__

    @classmethod
    def setUpClass(cls):
        """
        用例预处理，加载自定义插件等

        :return: 
        """
        pass

    def runTest(self):
        """
        testcase的正文，把要执行的测试和包括断言语句都写到这里

        """

        raise NotImplementedError

    @classmethod
    def tearDownClass(cls):
        """
        用例后处理

        :return: 
        """
        pass

    def shortDescription(self):
        """
        描述这个testcase的细节，如果需要的话就override这个方法
        
        :return: <str>
        """

        return super(PocoTestCase, self).shortDescription()

    def defaultTestResult(self):
        return PocoTestResult()

    @classmethod
    def set_result_collector(cls, collector):
        if type(cls) is not type:
            cls = cls.__class__
        cls._resule_collector = collector

    @classmethod
    def get_result_collector(cls):
        return cls._resule_collector

    @classmethod
    def add_result_emitter(cls, name, emitter):
        if type(cls) is not type:
            cls = cls.__class__
        if not cls._result_emitters:
            cls._result_emitters = {}
        cls._result_emitters[name] = emitter

    @classmethod
    def get_result_emitter(cls, name):
        return cls._result_emitters.get(name)

    @classmethod
    def register_addon(cls, addon):
        if type(cls) is not type:
            cls = cls.__class__
        if not cls._addons:
            cls._addons = []
        cls._addons.append(addon)

    @classmethod
    def register_addin(cls, v):
        warnings.warn('`register_addin` is deprecated. Please use `register_addon` instead.')
        return cls.register_addon(v)

    def run(self, result=None):
        result = result or self.defaultTestResult()
        if not isinstance(result, PocoTestResult):
            raise TypeError('Test result class should be subclass of PocoTestResult. '
                            'Current test result instance is "{}".'.format(result))

        # 自动把当前脚本add到collector的脚本watch列表里
        collector = self.get_result_collector()
        collector.add_testcase_file(self.test_case_filename)

        # register addon
        for addon in self._addons:
            addon.initialize(self)

        self.meta_info_emitter.test_started(self.__class__.__name__)

        # start result emitter
        for name, emitter in self._result_emitters.items():
            try:
                emitter.start()
            except Exception as e:
                warnings.warn('Fail to start result emitter: "{}". Error message: \n"{}"'
                              .format(emitter.__class__.__name__, e.message))

        # run test
        ex = None
        ret = None
        try:
            ret = super(PocoTestCase, self).run(result)
        except Exception as e:
            ex = e

        assertionRecorder = self.get_result_emitter('assertionRecorder')
        for _, exc_type, e, tb in result.detail_errors:
            assertionRecorder.traceback(exc_type, e, tb)

        # stop result emitter
        for name, emitter in self._result_emitters.items():
            try:
                emitter.stop()
            except:
                pass

        self.meta_info_emitter.test_ended(self.__class__.__name__)

        # handle result
        if ex is not None:
            self.meta_info_emitter.test_fail(self.__class__.__name__)
            raise ex
        else:
            self.meta_info_emitter.test_succeed(self.__class__.__name__)
            return ret

    def fail(self, msg=None):
        assertionRecorder = self.get_result_emitter('assertionRecorder')
        try:
            return super(PocoTestCase, self).fail(msg)
        except:
            exc_type, e, exc_tb = sys.exc_info()
            assertionRecorder.fail(msg, str(e), traceback.format_exc())
            raise exc_type, msg, exc_tb

    # assertions
    def assertTrue(self, expr, msg=None):
        assertionRecorder = self.get_result_emitter('assertionRecorder')
        assertionRecorder.assert_('True', [expr], msg)

        if not expr:
            msg = self._formatMessage(msg, "%s is not true" % safe_repr(expr))
            self.fail(msg)

    def assertFalse(self, expr, msg=None):
        assertionRecorder = self.get_result_emitter('assertionRecorder')
        assertionRecorder.assert_('False', [expr], msg)

        if expr:
            msg = self._formatMessage(msg, "%s is not false" % safe_repr(expr))
            self.fail(msg)

    def assertEqual(self, first, second, msg=None):
        assertionRecorder = self.get_result_emitter('assertionRecorder')
        assertionRecorder.assert_('Equal', [first, second], msg)
        return super(PocoTestCase, self).assertEqual(first, second, msg)

    def assertNotEqual(self, first, second, msg=None):
        assertionRecorder = self.get_result_emitter('assertionRecorder')
        assertionRecorder.assert_('NotEqual', [first, second], msg)
        return super(PocoTestCase, self).assertNotEqual(first, second, msg)

    def assertLess(self, a, b, msg=None):
        assertionRecorder = self.get_result_emitter('assertionRecorder')
        assertionRecorder.assert_('Less', [a, b], msg)
        return super(PocoTestCase, self).assertLess(a, b, msg)

    def assertLessEqual(self, a, b, msg=None):
        assertionRecorder = self.get_result_emitter('assertionRecorder')
        assertionRecorder.assert_('LessEqual', [a, b], msg)
        return super(PocoTestCase, self).assertLessEqual(a, b, msg)

    def assertGreater(self, a, b, msg=None):
        assertionRecorder = self.get_result_emitter('assertionRecorder')
        assertionRecorder.assert_('Greater', [a, b], msg)
        return super(PocoTestCase, self).assertGreater(a, b, msg)

    def assertGreaterEqual(self, a, b, msg=None):
        assertionRecorder = self.get_result_emitter('assertionRecorder')
        assertionRecorder.assert_('GreaterEqual', [a, b], msg)
        return super(PocoTestCase, self).assertGreaterEqual(a, b, msg)

    def assertIn(self, member, container, msg=None):
        assertionRecorder = self.get_result_emitter('assertionRecorder')
        assertionRecorder.assert_('In', [member, container], msg)
        return super(PocoTestCase, self).assertIn(member, container, msg)

    def assertNotIn(self, member, container, msg=None):
        assertionRecorder = self.get_result_emitter('assertionRecorder')
        assertionRecorder.assert_('NotIn', [member, container], msg)
        return super(PocoTestCase, self).assertNotIn(member, container, msg)
