import os

import pytest

import autofit as af
import test.mock


@pytest.fixture(name="results")
def make_results_collection():
    results = af.ResultsCollection()

    results.add("first phase", "one")
    results.add("second phase", "two")

    return results


class TestResultsCollection(object):
    def test_with_name(self, results):
        assert results.from_phase("first phase") == "one"
        assert results.from_phase("second phase") == "two"

    def test_with_index(self, results):
        assert results[0] == "one"
        assert results[1] == "two"
        assert results.first == "one"
        assert results.last == "two"
        assert len(results) == 2

    def test_missing_result(self, results):
        with pytest.raises(af.exc.PipelineException):
            results.from_phase("third phase")


class MockPhase(af.AbstractPhase):
    def make_result(self, result, analysis):
        pass

    def __init__(self, phase_name, optimizer=None):
        super().__init__(phase_name)
        self.optimizer = optimizer or af.NonLinearOptimizer(phase_name)
        self.phase_path = phase_name
        self.phase_tag = phase_name

    def save_metadata(self, data_name, pipeline_name):
        pass


class MockHyperPhase(MockPhase, af.HyperPhase):
    pass


class TestPipeline(object):
    def test_hyper_phase_naming(self):
        pipeline = af.Pipeline("name", MockPhase("one"), MockHyperPhase("hyper"))

        # noinspection PyUnusedLocal,PyShadowingNames
        def func(phase, results):
            pass

        results = pipeline.run_function(func)

        assert len(results) == 2
        assert "hyper" not in results

    def test_hyper_phase_must_be_after_normal_phase(self):
        pipeline = af.Pipeline("name", MockHyperPhase("hyper"), MockPhase("one"))

        # noinspection PyUnusedLocal,PyShadowingNames
        def func(phase, results):
            pass

        with pytest.raises(af.exc.PipelineException):
            pipeline.run_function(func)

    def test_unique_phases(self):
        af.Pipeline("name", MockPhase("one"), MockPhase("two"))
        with pytest.raises(af.exc.PipelineException):
            af.Pipeline("name", MockPhase("one"), MockPhase("one"))

    def test_optimizer_assertion(self):
        optimizer = af.NonLinearOptimizer("Phase Name")
        optimizer.variable.profile = test.mock.GeometryProfile
        phase = MockPhase("phase_name", optimizer)

        try:
            os.makedirs(phase.make_path())
        except FileExistsError:
            pass

        phase.save_optimizer_for_phase()
        phase.assert_optimizer_pickle_matches_for_phase()

        optimizer.variable.profile.centre_0 = af.UniformPrior()

        with pytest.raises(af.exc.PipelineException):
            phase.assert_optimizer_pickle_matches_for_phase()

    def test_name_composition(self):
        first = af.Pipeline("first")
        second = af.Pipeline("second")

        assert (first + second).pipeline_name == "first + second"

    def test_assert_and_save_pickle(self):
        phase = af.AbstractPhase("name")

        phase.assert_and_save_pickle()
        phase.assert_and_save_pickle()

        phase.variable.galaxy = test.mock.Galaxy

        with pytest.raises(af.exc.PipelineException):
            phase.assert_and_save_pickle()


# noinspection PyUnresolvedReferences
class TestPhasePipelineName(object):
    def test_name_stamping(self):
        one = MockPhase("one")
        two = MockPhase("two")
        af.Pipeline("name", one, two)

        assert one.pipeline_name == "name"
        assert two.pipeline_name == "name"

    def test_no_restamping(self):
        one = MockPhase("one")
        two = MockPhase("two")
        pipeline_one = af.Pipeline("one", one)
        pipeline_two = af.Pipeline("two", two)

        composed_pipeline = pipeline_one + pipeline_two

        assert composed_pipeline[0].pipeline_name == "one"
        assert composed_pipeline[1].pipeline_name == "two"

        assert one.pipeline_name == "one"
        assert two.pipeline_name == "two"
