#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Collection of tests for :mod:`orion.core.worker.strategies`."""
import logging

import pytest

from orion.core.worker.strategy import (
    MaxParallelStrategy, MeanParallelStrategy, NoParallelStrategy, Strategy, StubParallelStrategy)
from orion.core.worker.trial import Trial


@pytest.fixture
def observations():
    """10 objective observations"""
    points = [i for i in range(10)]
    results = [{'objective': points[i]} for i in range(10)]

    return points, results


@pytest.fixture
def incomplete_trial():
    """Return a single trial without results"""
    return Trial(params=[{'name': 'a', 'type': 'integer', 'value': 6}])


@pytest.fixture
def corrupted_trial():
    """Return a corrupted trial with results but status reserved"""
    return Trial(params=[{'name': 'a', 'type': 'integer', 'value': 6}],
                 results=[{'name': 'objective', 'type': 'objective', 'value': 1}],
                 status='reserved')


strategies = [
    'MaxParallelStrategy', 'MeanParallelStrategy', 'NoParallelStrategy', 'StubParallelStrategy']


@pytest.mark.parametrize('strategy', strategies)
def test_handle_corrupted_trials(caplog, strategy, corrupted_trial):
    """Verify that corrupted trials are handled properly"""
    with caplog.at_level(logging.WARNING, logger="orion.core.worker.strategy"):
        lie = Strategy(strategy).lie(corrupted_trial)

    match = "Trial `{}` has an objective but status is not completed".format(corrupted_trial.id)
    assert match in caplog.text

    assert lie is not None
    assert lie.value == corrupted_trial.objective.value


@pytest.mark.parametrize('strategy', strategies)
def test_handle_uncorrupted_trials(caplog, strategy, incomplete_trial):
    """Verify that no warning is logged if trial is valid"""
    with caplog.at_level(logging.WARNING, logger="orion.core.worker.strategy"):
        Strategy(strategy).lie(incomplete_trial)

    assert "Trial `{}` has an objective but status is not completed" not in caplog.text


class TestStrategyFactory:
    """Test creating a parallel strategy with the Strategy class"""

    def test_create_noparallel(self):
        """Test creating a NoParallelStrategy class"""
        strategy = Strategy('NoParallelStrategy')
        assert isinstance(strategy, NoParallelStrategy)

    def test_create_meanparallel(self):
        """Test creating a MeanParallelStrategy class"""
        strategy = Strategy('MeanParallelStrategy')
        assert isinstance(strategy, MeanParallelStrategy)


class TestParallelStrategies:
    """Test the different parallel strategy methods"""

    def test_max_parallel_strategy(self, observations, incomplete_trial):
        """Test that MaxParallelStrategy lies using the max"""
        points, results = observations

        strategy = MaxParallelStrategy()
        strategy.observe(points, results)
        lying_result = strategy.lie(incomplete_trial)

        max_value = max(result['objective'] for result in results)
        assert lying_result.value == max_value

    def test_mean_parallel_strategy(self, observations, incomplete_trial):
        """Test that MeanParallelStrategy lies using the mean"""
        points, results = observations

        strategy = MeanParallelStrategy()
        strategy.observe(points, results)
        lying_result = strategy.lie(incomplete_trial)

        mean_value = (sum(result['objective'] for result in results) /
                      float(len(results)))
        assert lying_result.value == mean_value

    def test_no_parallel_strategy(self, observations, incomplete_trial):
        """Test that NoParallelStrategy lies outputs None"""
        points, results = observations

        strategy = NoParallelStrategy()
        strategy.observe(points, results)
        lying_result = strategy.lie(incomplete_trial)
        assert lying_result is None

    def test_stub_parallel_strategy(self, observations, incomplete_trial):
        """Test that NoParallelStrategy lies outputs None"""
        points, results = observations

        strategy = StubParallelStrategy()
        strategy.observe(points, results)
        lying_result = strategy.lie(incomplete_trial)
        assert lying_result.value is None
