#coding:utf-8
"""
Экшены для работы в асинхронном режиме
++++++++++++++++++++++++++++++++++++++
"""
__author__ = 'ZIgi'

from abc import ABCMeta, abstractmethod
from threading import Thread
import json

from django import http
from django.conf import settings
try:
    from django.utils.log import logger
except ImportError:
    from django.utils.log import getLogger
    logger = getLogger('django')

from m3.actions import Action, ACD
from m3.actions.results import ActionResult

# !!!!!
# для работы данного модуля необходимо наличие приложения 'm3_mutex'
# в INSTALLED_APPS приложения

# TODO: переделать по правильному - платформа не должна импортировать
# модули контрибов

if not 'm3_mutex' in settings.INSTALLED_APPS:
    # При сборке документации внешняя Django ничего не знает про m3_mutex
    logger.warning(
        u'For working async operations "m3_mutex" '
        u'must be define in INSTALLED_APPS')
    # raise ImportError(
    # u'For working async operations "m3_mutex" must'
    # ' be define in INSTALLED_APPS')

try:
    from m3_mutex import (
        capture_mutex, release_mutex, request_mutex,
        MutexID, MutexOwner, MutexBusy, MutexState, TimeoutAutoRelease)
except ImportError:
    logger.warning(u'm3_mutex import error')


class IBackgroundWorker(Thread):
    '''
    Абстрактный класс для исполнения кода в фоновом режиме.
    Соответсвующие методы должны быть определены разработчиком.
    Тк класс представляет собой наследник Thread, нужно понимать
    что фактически при использовании будет создан новый тред,
    и учитывать это при доступе к ресурсам приложения, кои могут
    быть модифицированы другими тредами. Методы stop, start и ping
    должны возвращать экземпляры  AsyncOperationResult.
    Вообще говоря, можно реализовать данный класс подругому,
    главное чтобы был имплементирован интерфейс
    (например если есть нужда использовать мутекс на уровне
    нескольких инстансов приложений)
    '''
    __metaclass__ = ABCMeta

    def __init__(self, boundary='', context=None, *args, **kwargs):
        super(IBackgroundWorker, self).__init__()
        self.boundary = boundary
        self.context = context

    def start(self):
        '''
        Запускает исполнение кода. Метод не абстрактный, при
        замещении обязательно вызывать суперкласс
        '''
        super(IBackgroundWorker, self).start()

    @abstractmethod
    def run(self):
        '''
        Метод вызывается после вызоыва метода start() в новом потоке,
        здесь фактически следует писать собственный код
        '''
        raise NotImplementedError(
            'Method run() must be overrided in  child class')

    @abstractmethod
    def stop(self):
        '''
        Метод остановки операции
        '''
        raise NotImplementedError(
            'Method stop() must be overrided in  child class')

    @abstractmethod
    def request(self):
        '''
        Запрос состояния операции
        '''
        raise NotImplementedError(
            'Method request() must be overrided in  child class')

    @abstractmethod
    def result(self):
        '''
        Запрос на получение результата асинхронной операции
        '''
        raise NotImplementedError(
            'Method result() must be overrided in  child class')

    # ------------------------------------------------------------------------
    # Методы работы c глобальными блокировками операции с указанным инстансом.
    # В базовом классе глобальная блокировка реализована с помощью семафоров
    # ------------------------------------------------------------------------
    MUTEX_GROUP = 'asyncop'
    RESULT_MUTEX_GROUP = 'asyncop-result'

    MUTEX_TIMEOUT = 600
    RESULT_MUTEX_TIMEOUT = 300

    def _mutex_id(self):
        return MutexID(group=IBackgroundWorker.MUTEX_GROUP, id=self.boundary)

    def _mutex_owner(self):
        return MutexOwner(
            name=IBackgroundWorker.MUTEX_GROUP, session_id=self.boundary)

    def _result_mutex_id(self):
        return MutexID(
            group=IBackgroundWorker.RESULT_MUTEX_GROUP, id=self.boundary)

    def _result_mutex_owner(self):
        return MutexOwner(
            name=IBackgroundWorker.RESULT_MUTEX_GROUP,
            session_id=self.boundary)

    def lock(self):
        '''
        Устанавливает глобальную блокировку по инстансу с использованием
        механизма мютексов
        '''
        try:
            capture_mutex(
                mutex_id=self._mutex_id(), owner=self._mutex_owner(),
                auto_release=TimeoutAutoRelease(timeout=self.MUTEX_TIMEOUT))
        except MutexBusy:
            raise Exception(
                u'asyncop: Не удалось установить'
                u' глобальную блокировку операции.')

    def unlock(self):
        '''
        Освобождает глобальную блокировку состояния
        '''
        release_mutex(mutex_id=self._mutex_id(), owner=self._mutex_owner())

    def check_state(self):
        '''
        Проверяет состояние глобальной блокировки операции.
        Возвращает кортеж из двух элементов (is_active, status_data),
        где is_active=True/False - показывает активность
        установленной блокировки, и status_data - произвольный
        объект (чаще строка), в котором
        находится описание состоания операции.
        '''
        mutex_state, mutex = request_mutex(self._mutex_id())
        return (
            (mutex_state != MutexState.FREE, mutex.status_data)
            if mutex is not None else '')

    def refresh_state(self, status_data):
        '''
        Обновляет состояние блокировки
        '''
        try:
            capture_mutex(
                mutex_id=self._mutex_id(), owner=self._mutex_owner(),
                status_data=status_data,
                auto_release=TimeoutAutoRelease(timeout=self.MUTEX_TIMEOUT))
        except MutexBusy:
            raise Exception(
                u'asyncop: Не удалось установить'
                u' глобальную блокировку операции.')

    def lock_result(self, result):
        '''
        Блокирует результат выполнения операции
        '''
        try:
            capture_mutex(
                mutex_id=self._result_mutex_id(),
                owner=self._result_mutex_owner(),
                auto_release=TimeoutAutoRelease(
                    timeout=self.RESULT_MUTEX_TIMEOUT),
                status_data=result or '')
        except MutexBusy:
            raise Exception(
                u'asyncop: Не удалось сохранить'
                u' результат выполнения оajyjперации.')

    def unlock_result(self):
        release_mutex(
            mutex_id=self._result_mutex_id(),
            owner=self._result_mutex_owner(),)

    def request_result(self):
        mutex_state, mutex = request_mutex(self._result_mutex_id())
        return (
            (mutex_state != MutexState.FREE, mutex.status_data)
            if mutex is not None else '')


class AsyncOperationResult(ActionResult):
    '''
    Результат выполнения асинхронной операции.
    '''

    def __init__(self, value=0.0, text='', alive=True):
        self.value = value
        self.text = text
        self.alive = alive

    def get_http_response(self):
        data = json.dumps({
            'text': self.text,
            'value': self.value,
            'alive': self.alive,
        })
        return http.HttpResponse(data, mimetype='application/json')


class AsyncAction(Action):
    '''
    Экшен обработки запросов с клиента. В данном варианте инстанс
    класса воркера общий для всех http запросов экземпляра приложения,
    поэтому если один пользователь начал фоновую операцию, все пользователи
    этого сервера будут видеть такой же прогресс. Можно с помощью словаря
    сессий, где бы хранились инстансы воркеров, реализовать для каждого
    пользователя свою операцию, или же с помощью мутексов глобальную
    блокировку операции на уровне приложения. При наследовании должен
    быть определен атрибут worker_cls - класс
    наследующий/имплементирующий IBackgroundWorker
    '''
    COMMAND_START = 'start'
    COMMAND_STOP = 'stop'
    COMMAND_REQUEST = 'request'
    COMMAND_RESULT = 'result'

    worker_cls = None

    def __init__(self):
        super(AsyncAction, self).__init__()

    def context_declaration(self):
        return [
            ACD(name='command', type=str, required=True),
            ACD(name='boundary', type=str, required=True,
                default='default-boundary'), ]

    def _worker_instance(self, request, context, *args, **kwargs):
        '''
        Метод, возвращающий экземпляр воркера фоновой операции.
        Данный метод можно переопределять для случаев, когда в
        воркер необходимо передавать дополнительные параметры.
        '''
        return self.worker_cls(boundary=context.boundary, context=context)

    def run(self, request, context):

        if self.worker_cls is None:
            raise NotImplementedError('Worker class is not defined')

        if context.command == self.COMMAND_START:
            return self.start_operation(request, context)
        elif context.command == self.COMMAND_STOP:
            return self.stop_operation(request, context)
        elif context.command == self.COMMAND_REQUEST:
            return self.request_operation(request, context)
        elif context.command == self.COMMAND_RESULT:
            return self.result_operation(request, context)

    def start_operation(self, request, context):
        worker_instance = self._worker_instance(request, context)
        # если операция уже запущенна, то перезапустим
        # надо подумать насколько корректно такое поведение
        worker_instance.stop()

        return worker_instance.start()

    def stop_operation(self, request, context):
        return self._worker_instance(request, context).stop()

    def request_operation(self, request, context):
        return self._worker_instance(request, context).request()

    def result_operation(self, request, context):
        return self._worker_instance(request, context).result()
