#!/usr/bin/env python
'''
Tests for `latexipy` package.

'''
from functools import partial
import inspect
import math
from unittest.mock import patch

import matplotlib as mpl
mpl.use('Agg')
import matplotlib.pyplot as plt
import pytest

import latexipy as lp
from latexipy._latexipy import INCH_PER_POINT, GOLDEN_RATIO, MAX_HEIGHT_INCH


class TestLatexify:
    def test_defaults(self):
        with patch('matplotlib.rcParams.update') as mock_update, \
                patch('matplotlib.pyplot.switch_backend') as mock_switch:
            lp.latexify()

            mock_update.assert_called_once_with(lp.PARAMS)
            mock_switch.assert_called_once_with('pgf')

    def test_custom_params(self):
        with patch('matplotlib.rcParams.update') as mock_update, \
                patch('matplotlib.pyplot.switch_backend') as mock_switch:
            params = {'param_a': 1, 'param_b': 2}
            lp.latexify(params)

            mock_update.assert_called_once_with(params)
            mock_switch.assert_called_once_with('pgf')

    def test_custom_backend(self):
        with patch('matplotlib.rcParams.update') as mock_update, \
                patch('matplotlib.pyplot.switch_backend') as mock_switch:
            lp.latexify(new_backend='QtAgg')

            mock_update.assert_called_once_with(lp.PARAMS)
            mock_switch.assert_called_once_with('QtAgg')

    def test_raises_error_on_bad_backend(self):
        with patch('matplotlib.rcParams.update') as mock_update:
            with pytest.raises(ValueError):
                lp.latexify(new_backend='foo')

            mock_update.assert_called_once_with(lp.PARAMS)


def test_revert():
    with patch('matplotlib.rcParams.update') as mock_update, \
            patch('matplotlib.pyplot.switch_backend') as mock_switch:
        lp.latexify()
        lp.revert()
        mock_update.assert_called_with(dict(plt.rcParams))
        mock_switch.assert_called_with(plt.get_backend())


class TestTempParams:
    def test_defaults(self):
        with patch('matplotlib.rcParams.update') as mock_update, \
                patch('matplotlib.pyplot.switch_backend') as mock_switch:
            old_params = dict(plt.rcParams)
            with lp.temp_params():
                mock_update.assert_called_with(old_params)
            mock_update.assert_called_with(old_params)

    def test_font_size(self):
        with patch('matplotlib.rcParams.update') as mock_update, \
                patch('matplotlib.pyplot.switch_backend') as mock_switch:
            old_params = dict(plt.rcParams)
            with lp.temp_params(font_size=10):
                called_with = mock_update.call_args[0][0]
                print(called_with)
                assert all(called_with[k] == 10
                           for k in lp.PARAMS if 'size' in k)
            mock_update.assert_called_with(old_params)

    def test_params_dict(self):
        with patch('matplotlib.rcParams.update') as mock_update, \
                patch('matplotlib.pyplot.switch_backend') as mock_switch:
            old_params = dict(plt.rcParams)
            with lp.temp_params(params_dict={'font.family': 'sans-serif'}):
                called_with = mock_update.call_args[0][0]
                assert called_with['font.family'] == 'sans-serif'
            mock_update.assert_called_with(old_params)

    def test_params_dict_after_font_size(self):
        with patch('matplotlib.rcParams.update') as mock_update, \
                patch('matplotlib.pyplot.switch_backend') as mock_switch:
            old_params = dict(plt.rcParams)
            with lp.temp_params(font_size=10, params_dict={
                    'axes.labelsize': 12,
                    'legend.fontsize': 12,
                    }):
                called_with = mock_update.call_args[0][0]
                assert called_with['font.size'] == 10
                assert called_with['axes.labelsize'] == 12
                assert called_with['axes.titlesize'] == 10
                assert called_with['legend.fontsize'] == 12
                assert called_with['xtick.labelsize'] == 10
                assert called_with['ytick.labelsize'] == 10

            mock_update.assert_called_with(old_params)


class TestFigureSize:
    def setup(self):
        self.width = 345 * 0.9 * INCH_PER_POINT

    def test_defaults(self):
        height = GOLDEN_RATIO * self.width
        assert lp.figure_size() == (self.width, height)

    def test_ratio_no_height(self):
        assert lp.figure_size(ratio=1) == (self.width, self.width)
        assert lp.figure_size(ratio=0.5) == (self.width, self.width/2)

    def test_ratio_height(self):
        dimensions = (self.width, self.width)
        assert lp.figure_size(ratio=1, height=5) == dimensions

    def test_height_no_ratio(self):
        assert lp.figure_size(height=5) == (self.width, 5)

    def test_height_too_high(self):
        with pytest.warns(UserWarning):
            height = MAX_HEIGHT_INCH + 1
            assert lp.figure_size(height=height) == (self.width,
                                                     MAX_HEIGHT_INCH)

    def test_columns(self):
        width = self.width / 2
        height = GOLDEN_RATIO * width
        assert lp.figure_size(n_columns=2) == (width, height)


class TestSaveFigure:
    def setup(self):
        self.f = partial(lp.save_figure, 'filename', 'directory', ['png'])

    def test_raises_error_if_directory_does_not_exist(self):
        with patch('matplotlib.pyplot.tight_layout'), \
                patch('matplotlib.pyplot.savefig',
                      side_effect=FileNotFoundError):
            with pytest.raises(FileNotFoundError):
                self.f(mkdir=False)

    def test_raises_error_if_directory_is_file(self):
        with patch('matplotlib.pyplot.tight_layout'), \
                patch('pathlib.Path.is_file', return_value=True):
            with pytest.raises(NotADirectoryError):
                self.f()

    def test_raises_error_if_no_permission_directory_does_not_exist(self):
        with patch('matplotlib.pyplot.tight_layout'), \
                patch('pathlib.Path.mkdir', side_effect=PermissionError), \
                patch('matplotlib.pyplot.savefig'):
            with pytest.raises(PermissionError):
                self.f()

    def test_raises_error_if_no_permission_directory_exists(self):
        with patch('matplotlib.pyplot.tight_layout'), \
                patch('pathlib.Path.mkdir'), \
                patch('matplotlib.pyplot.savefig',
                      side_effect=PermissionError):
            with pytest.raises(PermissionError):
                self.f()

    def test_raises_error_if_file_format_not_supported(self):
        with patch('matplotlib.pyplot.tight_layout'), \
                patch('pathlib.Path.mkdir'):
            with pytest.raises(ValueError):
                lp.save_figure('filename', 'directory', exts=['nonexistent'])


    def test_warns_if_no_figures(self):
        with patch('pathlib.Path.mkdir'), \
                patch('matplotlib.pyplot.savefig'):
            with pytest.warns(UserWarning):
                self.f()

    def test_saves_if_all_good(self):
        with patch('matplotlib.pyplot.tight_layout'), \
                patch('pathlib.Path.mkdir'), \
                patch('matplotlib.pyplot.savefig') as mock_savefig:
            self.f()
            assert mock_savefig.called_once()

    def test_saves_if_from_context_manager(self):
        with patch('matplotlib.pyplot.tight_layout'), \
                patch('pathlib.Path.mkdir'), \
                patch('matplotlib.pyplot.savefig') as mock_savefig:
            self.f(from_context_manager=True)
            assert mock_savefig.called_once()


class TestFigure:
    def test_default_size_is_figure_size(self):
        default_size = lp.figure_size()

        with patch('matplotlib.figure.Figure.set_size_inches') as mock_set, \
                patch('latexipy._latexipy.save_figure'):
            with lp.figure('filename'):
                pass

            mock_set.assert_called_once_with(*default_size)

    def test_figure_size_is_kwarg_size(self):
        size = (6, 6)
        with patch('matplotlib.figure.Figure.set_size_inches') as mock_set, \
                patch('latexipy._latexipy.save_figure'):
            with lp.figure('filename', size=size):
                pass

            mock_set.assert_called_once_with(*size)

    def test_parameters_passed_all_kwargs_default(self):
        params = inspect.signature(lp.figure).parameters

        with patch('matplotlib.figure.Figure.set_size_inches'), \
                patch('latexipy._latexipy.save_figure') as mock_save_figure:
            with lp.figure('filename'):
                pass

            mock_save_figure.assert_called_once_with(
                filename='filename',
                directory=params['directory'].default,
                exts=params['exts'].default,
                mkdir=params['mkdir'].default,
                from_context_manager=True,
            )

    def test_parameters_passed_custom_kwargs(self):
        params = inspect.signature(lp.figure).parameters

        with patch('matplotlib.figure.Figure.set_size_inches'), \
                patch('latexipy._latexipy.save_figure') as mock_save_figure:
            with lp.figure('filename', directory='directory', exts='exts',
                           mkdir='mkdir'):
                pass

            mock_save_figure.assert_called_once_with(
                filename='filename',
                directory='directory',
                exts='exts',
                mkdir='mkdir',
                from_context_manager=True,
            )