import numpy as np
from numpy.testing import assert_allclose
import pytest

import matplotlib as mpl
from matplotlib import pyplot as plt
from matplotlib.testing.decorators import image_comparison, check_figures_equal


@image_comparison(['polar_axes'], style='default', tol=0.012)
def test_polar_annotations():
    # You can specify the xypoint and the xytext in different positions and
    # coordinate systems, and optionally turn on a connecting line and mark the
    # point with a marker.  Annotations work on polar axes too.  In the example
    # below, the xy point is in native coordinates (xycoords defaults to
    # 'data').  For a polar axes, this is in (theta, radius) space.  The text
    # in this example is placed in the fractional figure coordinate system.
    # Text keyword args like horizontal and vertical alignment are respected.

    # Setup some data
    r = np.arange(0.0, 1.0, 0.001)
    theta = 2.0 * 2.0 * np.pi * r

    fig = plt.figure()
    ax = fig.add_subplot(polar=True)
    line, = ax.plot(theta, r, color='#ee8d18', lw=3)
    line, = ax.plot((0, 0), (0, 1), color="#0000ff", lw=1)

    ind = 800
    thisr, thistheta = r[ind], theta[ind]
    ax.plot([thistheta], [thisr], 'o')
    ax.annotate('a polar annotation',
                xy=(thistheta, thisr),  # theta, radius
                xytext=(0.05, 0.05),    # fraction, fraction
                textcoords='figure fraction',
                arrowprops=dict(facecolor='black', shrink=0.05),
                horizontalalignment='left',
                verticalalignment='baseline',
                )

    ax.tick_params(axis='x', tick1On=True, tick2On=True, direction='out')


@image_comparison(['polar_coords'], style='default', remove_text=True,
                  tol=0.014)
def test_polar_coord_annotations():
    # You can also use polar notation on a cartesian axes.  Here the native
    # coordinate system ('data') is cartesian, so you need to specify the
    # xycoords and textcoords as 'polar' if you want to use (theta, radius).
    el = mpl.patches.Ellipse((0, 0), 10, 20, facecolor='r', alpha=0.5)

    fig = plt.figure()
    ax = fig.add_subplot(aspect='equal')

    ax.add_artist(el)
    el.set_clip_box(ax.bbox)

    ax.annotate('the top',
                xy=(np.pi/2., 10.),      # theta, radius
                xytext=(np.pi/3, 20.),   # theta, radius
                xycoords='polar',
                textcoords='polar',
                arrowprops=dict(facecolor='black', shrink=0.05),
                horizontalalignment='left',
                verticalalignment='baseline',
                clip_on=True,  # clip to the axes bounding box
                )

    ax.set_xlim(-20, 20)
    ax.set_ylim(-20, 20)


@image_comparison(['polar_alignment.png'])
def test_polar_alignment():
    # Test changing the vertical/horizontal alignment of a polar graph.
    angles = np.arange(0, 360, 90)
    grid_values = [0, 0.2, 0.4, 0.6, 0.8, 1]

    fig = plt.figure()
    rect = [0.1, 0.1, 0.8, 0.8]

    horizontal = fig.add_axes(rect, polar=True, label='horizontal')
    horizontal.set_thetagrids(angles)

    vertical = fig.add_axes(rect, polar=True, label='vertical')
    vertical.patch.set_visible(False)

    for i in range(2):
        fig.axes[i].set_rgrids(
            grid_values, angle=angles[i],
            horizontalalignment='left', verticalalignment='top')


def test_polar_twice():
    fig = plt.figure()
    plt.polar([1, 2], [.1, .2])
    plt.polar([3, 4], [.3, .4])
    assert len(fig.axes) == 1, 'More than one polar Axes created.'


@check_figures_equal()
def test_polar_wrap(fig_test, fig_ref):
    ax = fig_test.add_subplot(projection="polar")
    ax.plot(np.deg2rad([179, -179]), [0.2, 0.1])
    ax.plot(np.deg2rad([2, -2]), [0.2, 0.1])
    ax = fig_ref.add_subplot(projection="polar")
    ax.plot(np.deg2rad([179, 181]), [0.2, 0.1])
    ax.plot(np.deg2rad([2, 358]), [0.2, 0.1])


@check_figures_equal()
def test_polar_units_1(fig_test, fig_ref):
    import matplotlib.testing.jpl_units as units
    units.register()
    xs = [30.0, 45.0, 60.0, 90.0]
    ys = [1.0, 2.0, 3.0, 4.0]

    plt.figure(fig_test.number)
    plt.polar([x * units.deg for x in xs], ys)

    ax = fig_ref.add_subplot(projection="polar")
    ax.plot(np.deg2rad(xs), ys)
    ax.set(xlabel="deg")


@check_figures_equal()
def test_polar_units_2(fig_test, fig_ref):
    import matplotlib.testing.jpl_units as units
    units.register()
    xs = [30.0, 45.0, 60.0, 90.0]
    xs_deg = [x * units.deg for x in xs]
    ys = [1.0, 2.0, 3.0, 4.0]
    ys_km = [y * units.km for y in ys]

    plt.figure(fig_test.number)
    # test {theta,r}units.
    plt.polar(xs_deg, ys_km, thetaunits="rad", runits="km")
    assert isinstance(plt.gca().xaxis.get_major_formatter(),
                      units.UnitDblFormatter)

    ax = fig_ref.add_subplot(projection="polar")
    ax.plot(np.deg2rad(xs), ys)
    ax.xaxis.set_major_formatter(mpl.ticker.FuncFormatter("{:.12}".format))
    ax.set(xlabel="rad", ylabel="km")


@image_comparison(['polar_rmin'], style='default')
def test_polar_rmin():
    r = np.arange(0, 3.0, 0.01)
    theta = 2*np.pi*r

    fig = plt.figure()
    ax = fig.add_axes([0.1, 0.1, 0.8, 0.8], polar=True)
    ax.plot(theta, r)
    ax.set_rmax(2.0)
    ax.set_rmin(0.5)


@image_comparison(['polar_negative_rmin'], style='default')
def test_polar_negative_rmin():
    r = np.arange(-3.0, 0.0, 0.01)
    theta = 2*np.pi*r

    fig = plt.figure()
    ax = fig.add_axes([0.1, 0.1, 0.8, 0.8], polar=True)
    ax.plot(theta, r)
    ax.set_rmax(0.0)
    ax.set_rmin(-3.0)


@image_comparison(['polar_rorigin'], style='default')
def test_polar_rorigin():
    r = np.arange(0, 3.0, 0.01)
    theta = 2*np.pi*r

    fig = plt.figure()
    ax = fig.add_axes([0.1, 0.1, 0.8, 0.8], polar=True)
    ax.plot(theta, r)
    ax.set_rmax(2.0)
    ax.set_rmin(0.5)
    ax.set_rorigin(0.0)


@image_comparison(['polar_invertedylim.png'], style='default')
def test_polar_invertedylim():
    fig = plt.figure()
    ax = fig.add_axes([0.1, 0.1, 0.8, 0.8], polar=True)
    ax.set_ylim(2, 0)


@image_comparison(['polar_invertedylim_rorigin.png'], style='default')
def test_polar_invertedylim_rorigin():
    fig = plt.figure()
    ax = fig.add_axes([0.1, 0.1, 0.8, 0.8], polar=True)
    ax.yaxis.set_inverted(True)
    # Set the rlims to inverted (2, 0) without calling set_rlim, to check that
    # viewlims are correctly unstaled before draw()ing.
    ax.plot([0, 0], [0, 2], c="none")
    ax.margins(0)
    ax.set_rorigin(3)


@image_comparison(['polar_theta_position'], style='default')
def test_polar_theta_position():
    r = np.arange(0, 3.0, 0.01)
    theta = 2*np.pi*r

    fig = plt.figure()
    ax = fig.add_axes([0.1, 0.1, 0.8, 0.8], polar=True)
    ax.plot(theta, r)
    ax.set_theta_zero_location("NW", 30)
    ax.set_theta_direction('clockwise')


@image_comparison(['polar_rlabel_position'], style='default')
def test_polar_rlabel_position():
    fig = plt.figure()
    ax = fig.add_subplot(projection='polar')
    ax.set_rlabel_position(315)
    ax.tick_params(rotation='auto')


@image_comparison(['polar_theta_wedge'], style='default')
def test_polar_theta_limits():
    r = np.arange(0, 3.0, 0.01)
    theta = 2*np.pi*r

    theta_mins = np.arange(15.0, 361.0, 90.0)
    theta_maxs = np.arange(50.0, 361.0, 90.0)
    DIRECTIONS = ('out', 'in', 'inout')

    fig, axs = plt.subplots(len(theta_mins), len(theta_maxs),
                            subplot_kw={'polar': True},
                            figsize=(8, 6))

    for i, start in enumerate(theta_mins):
        for j, end in enumerate(theta_maxs):
            ax = axs[i, j]
            ax.plot(theta, r)
            if start < end:
                ax.set_thetamin(start)
                ax.set_thetamax(end)
            else:
                # Plot with clockwise orientation instead.
                ax.set_thetamin(end)
                ax.set_thetamax(start)
                ax.set_theta_direction('clockwise')
            ax.tick_params(tick1On=True, tick2On=True,
                           direction=DIRECTIONS[i % len(DIRECTIONS)],
                           rotation='auto')
            ax.yaxis.set_tick_params(label2On=True, rotation='auto')
            ax.xaxis.get_major_locator().base.set_params(  # backcompat
                steps=[1, 2, 2.5, 5, 10])


@check_figures_equal(extensions=["png"])
def test_polar_rlim(fig_test, fig_ref):
    ax = fig_test.subplots(subplot_kw={'polar': True})
    ax.set_rlim(top=10)
    ax.set_rlim(bottom=.5)

    ax = fig_ref.subplots(subplot_kw={'polar': True})
    ax.set_rmax(10.)
    ax.set_rmin(.5)


@check_figures_equal(extensions=["png"])
def test_polar_rlim_bottom(fig_test, fig_ref):
    ax = fig_test.subplots(subplot_kw={'polar': True})
    ax.set_rlim(bottom=[.5, 10])

    ax = fig_ref.subplots(subplot_kw={'polar': True})
    ax.set_rmax(10.)
    ax.set_rmin(.5)


def test_polar_rlim_zero():
    ax = plt.figure().add_subplot(projection='polar')
    ax.plot(np.arange(10), np.arange(10) + .01)
    assert ax.get_ylim()[0] == 0


def test_polar_no_data():
    plt.subplot(projection="polar")
    ax = plt.gca()
    assert ax.get_rmin() == 0 and ax.get_rmax() == 1
    plt.close("all")
    # Used to behave differently (by triggering an autoscale with no data).
    plt.polar()
    ax = plt.gca()
    assert ax.get_rmin() == 0 and ax.get_rmax() == 1


def test_polar_default_log_lims():
    plt.subplot(projection='polar')
    ax = plt.gca()
    ax.set_rscale('log')
    assert ax.get_rmin() > 0


def test_polar_not_datalim_adjustable():
    ax = plt.figure().add_subplot(projection="polar")
    with pytest.raises(ValueError):
        ax.set_adjustable("datalim")


def test_polar_gridlines():
    fig = plt.figure()
    ax = fig.add_subplot(polar=True)
    # make all major grid lines lighter, only x grid lines set in 2.1.0
    ax.grid(alpha=0.2)
    # hide y tick labels, no effect in 2.1.0
    plt.setp(ax.yaxis.get_ticklabels(), visible=False)
    fig.canvas.draw()
    assert ax.xaxis.majorTicks[0].gridline.get_alpha() == .2
    assert ax.yaxis.majorTicks[0].gridline.get_alpha() == .2


def test_get_tightbbox_polar():
    fig, ax = plt.subplots(subplot_kw={'projection': 'polar'})
    fig.canvas.draw()
    bb = ax.get_tightbbox(fig.canvas.get_renderer())
    assert_allclose(
        bb.extents, [107.7778,  29.2778, 539.7847, 450.7222], rtol=1e-03)


@check_figures_equal(extensions=["png"])
def test_polar_interpolation_steps_constant_r(fig_test, fig_ref):
    # Check that an extra half-turn doesn't make any difference -- modulo
    # antialiasing, which we disable here.
    p1 = (fig_test.add_subplot(121, projection="polar")
          .bar([0], [1], 3*np.pi, edgecolor="none", antialiased=False))
    p2 = (fig_test.add_subplot(122, projection="polar")
          .bar([0], [1], -3*np.pi, edgecolor="none", antialiased=False))
    p3 = (fig_ref.add_subplot(121, projection="polar")
          .bar([0], [1], 2*np.pi, edgecolor="none", antialiased=False))
    p4 = (fig_ref.add_subplot(122, projection="polar")
          .bar([0], [1], -2*np.pi, edgecolor="none", antialiased=False))


@check_figures_equal(extensions=["png"])
def test_polar_interpolation_steps_variable_r(fig_test, fig_ref):
    l, = fig_test.add_subplot(projection="polar").plot([0, np.pi/2], [1, 2])
    l.get_path()._interpolation_steps = 100
    fig_ref.add_subplot(projection="polar").plot(
        np.linspace(0, np.pi/2, 101), np.linspace(1, 2, 101))


def test_thetalim_valid_invalid():
    ax = plt.subplot(projection='polar')
    ax.set_thetalim(0, 2 * np.pi)  # doesn't raise.
    ax.set_thetalim(thetamin=800, thetamax=440)  # doesn't raise.
    with pytest.raises(ValueError,
                       match='angle range must be less than a full circle'):
        ax.set_thetalim(0, 3 * np.pi)
    with pytest.raises(ValueError,
                       match='angle range must be less than a full circle'):
        ax.set_thetalim(thetamin=800, thetamax=400)


def test_thetalim_args():
    ax = plt.subplot(projection='polar')
    ax.set_thetalim(0, 1)
    assert tuple(np.radians((ax.get_thetamin(), ax.get_thetamax()))) == (0, 1)
    ax.set_thetalim((2, 3))
    assert tuple(np.radians((ax.get_thetamin(), ax.get_thetamax()))) == (2, 3)


def test_default_thetalocator():
    # Ideally we would check AAAABBC, but the smallest axes currently puts a
    # single tick at 150° because MaxNLocator doesn't have a way to accept 15°
    # while rejecting 150°.
    fig, axs = plt.subplot_mosaic(
        "AAAABB.", subplot_kw={"projection": "polar"})
    for ax in axs.values():
        ax.set_thetalim(0, np.pi)
    for ax in axs.values():
        ticklocs = np.degrees(ax.xaxis.get_majorticklocs()).tolist()
        assert pytest.approx(90) in ticklocs
        assert pytest.approx(100) not in ticklocs


def test_axvspan():
    ax = plt.subplot(projection="polar")
    span = ax.axvspan(0, np.pi/4)
    assert span.get_path()._interpolation_steps > 1


@check_figures_equal(extensions=["png"])
def test_remove_shared_polar(fig_ref, fig_test):
    # Removing shared polar axes used to crash.  Test removing them, keeping in
    # both cases just the lower left axes of a grid to avoid running into a
    # separate issue (now being fixed) of ticklabel visibility for shared axes.
    axs = fig_ref.subplots(
        2, 2, sharex=True, subplot_kw={"projection": "polar"})
    for i in [0, 1, 3]:
        axs.flat[i].remove()
    axs = fig_test.subplots(
        2, 2, sharey=True, subplot_kw={"projection": "polar"})
    for i in [0, 1, 3]:
        axs.flat[i].remove()


def test_shared_polar_keeps_ticklabels():
    fig, axs = plt.subplots(
        2, 2, subplot_kw={"projection": "polar"}, sharex=True, sharey=True)
    fig.canvas.draw()
    assert axs[0, 1].xaxis.majorTicks[0].get_visible()
    assert axs[0, 1].yaxis.majorTicks[0].get_visible()
    fig, axs = plt.subplot_mosaic(
        "ab\ncd", subplot_kw={"projection": "polar"}, sharex=True, sharey=True)
    fig.canvas.draw()
    assert axs["b"].xaxis.majorTicks[0].get_visible()
    assert axs["b"].yaxis.majorTicks[0].get_visible()


def test_axvline_axvspan_do_not_modify_rlims():
    ax = plt.subplot(projection="polar")
    ax.axvspan(0, 1)
    ax.axvline(.5)
    ax.plot([.1, .2])
    assert ax.get_ylim() == (0, .2)


def test_cursor_precision():
    ax = plt.subplot(projection="polar")
    # Higher radii correspond to higher theta-precisions.
    assert ax.format_coord(0, 0.005) == "θ=0.0π (0°), r=0.005"
    assert ax.format_coord(0, .1) == "θ=0.00π (0°), r=0.100"
    assert ax.format_coord(0, 1) == "θ=0.000π (0.0°), r=1.000"
    assert ax.format_coord(1, 0.005) == "θ=0.3π (57°), r=0.005"
    assert ax.format_coord(1, .1) == "θ=0.32π (57°), r=0.100"
    assert ax.format_coord(1, 1) == "θ=0.318π (57.3°), r=1.000"
    assert ax.format_coord(2, 0.005) == "θ=0.6π (115°), r=0.005"
    assert ax.format_coord(2, .1) == "θ=0.64π (115°), r=0.100"
    assert ax.format_coord(2, 1) == "θ=0.637π (114.6°), r=1.000"


@image_comparison(['polar_log.png'], style='default')
def test_polar_log():
    fig = plt.figure()
    ax = fig.add_subplot(polar=True)

    ax.set_rscale('log')
    ax.set_rlim(1, 1000)

    n = 100
    ax.plot(np.linspace(0, 2 * np.pi, n), np.logspace(0, 2, n))


def test_polar_neg_theta_lims():
    fig = plt.figure()
    ax = fig.add_subplot(projection='polar')
    ax.set_thetalim(-np.pi, np.pi)
    labels = [l.get_text() for l in ax.xaxis.get_ticklabels()]
    assert labels == ['-180°', '-135°', '-90°', '-45°', '0°', '45°', '90°', '135°']
