# coding: utf-8

from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals

import time
import logging

import sandbox.common.types.task as common_task_types

from saas.library.python.common_functions import linear_delay_generator
from saas.library.python.sandbox.errors import SandboxTaskFailure


class SandboxTask(object):
    LOGGER = logging.getLogger(__name__)
    FINAL_STATES = common_task_types.Status.Group.BREAK + common_task_types.Status.Group.FINISH

    def __init__(self, sandbox, task_id):
        self.sandbox_client = sandbox
        self.id = task_id
        self._info = {}
        self._resources = []

    def __repr__(self):
        return 'SandboxTask({})'.format(self.id)

    @classmethod
    def from_task_info(cls, sandbox, task_info):
        task = cls(sandbox, task_info['id'])
        task._info = task_info
        return task

    def update_info(self):
        self._info = self.sandbox_client.task[self.id].read()

    def dict(self):
        if self._info is None:
            self.update_info()
        return self._info

    def __getattribute__(self, name):
        if name in {'type', 'owner', 'author', 'description', 'input_parameters'}:
            if self._info.get(name, None) is None:
                self.update_info()
            return self._info[name]
        else:
            return object.__getattribute__(self, name)

    @property
    def status(self):
        if self._info.get('status', None) is None or self._info['status'] not in self.FINAL_STATES:
            self.update_info()
        return self._info['status']

    def _get_raw_resources(self):
        return self.sandbox_client.task[self.id].resources.read()['items']

    @property
    def resources(self):
        from saas.library.python.sandbox.resource import SandboxResource
        for resource in self._get_raw_resources():
            yield SandboxResource.from_resource_info(sandbox=self.sandbox_client, resource_info=resource)

    @property
    def ready_resources(self):
        return filter(lambda r: r.ready, self.resources)

    def get_single_resource(self, selector_function):
        selected_resource = list(filter(selector_function, self.resources))
        if len(selected_resource) != 1:
            self.LOGGER.error('One resource required, got {} from filter'.format(selected_resource))
            raise ValueError('More than one resource selected')
        return selected_resource[0]

    @property
    def context(self):
        return self.sandbox_client.task[self.id].context.read()

    @property
    def finished(self):
        if self.status in self.FINAL_STATES:
            return True
        else:
            return False

    @property
    def finished_ok(self):
        if self.status in common_task_types.Status.Group.SUCCEED:
            return True
        else:
            return False

    def start(self):
        self.sandbox_client.batch.tasks.start.update([self.id])

    def wait(self, delay_generator=linear_delay_generator(3)):
        self.LOGGER.info('Waiting for task {} to complete...'.format(self.id))
        for sleep_delay in delay_generator:
            status = self.status
            if status in common_task_types.Status.Group.SUCCEED:
                self.LOGGER.info('Task %s completed successfully', self.id)
                return self
            elif status in self.FINAL_STATES:
                raise SandboxTaskFailure(
                    'Task {} finished unsuccessfully with final status {}'.format(self.id, self.status),
                    sandbox_task=self
                )
            else:
                self.LOGGER.debug('Waiting task to change state from %s. Next sleep %d', status, sleep_delay)
                time.sleep(sleep_delay)
