# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.

import json
import os
import warnings

from unittest import mock

import pytest

from IPython import display
from IPython.core.getipython import get_ipython
from IPython.utils.io import capture_output
from IPython.utils.tempdir import NamedFileInTemporaryDirectory
from IPython import paths as ipath
from IPython.testing.tools import AssertNotPrints

import IPython.testing.decorators as dec


def test_image_size():
    """Simple test for display.Image(args, width=x,height=y)"""
    thisurl = "http://www.google.fr/images/srpr/logo3w.png"
    img = display.Image(url=thisurl, width=200, height=200)
    assert '<img src="%s" width="200" height="200"/>' % (thisurl) == img._repr_html_()
    img = display.Image(url=thisurl, metadata={"width": 200, "height": 200})
    assert '<img src="%s" width="200" height="200"/>' % (thisurl) == img._repr_html_()
    img = display.Image(url=thisurl, width=200)
    assert '<img src="%s" width="200"/>' % (thisurl) == img._repr_html_()
    img = display.Image(url=thisurl)
    assert '<img src="%s"/>' % (thisurl) == img._repr_html_()
    img = display.Image(url=thisurl, unconfined=True)
    assert '<img src="%s" class="unconfined"/>' % (thisurl) == img._repr_html_()


def test_image_mimes():
    fmt = get_ipython().display_formatter.format
    for format in display.Image._ACCEPTABLE_EMBEDDINGS:
        mime = display.Image._MIMETYPES[format]
        img = display.Image(b"garbage", format=format)
        data, metadata = fmt(img)
        assert sorted(data) == sorted([mime, "text/plain"])


def test_geojson():
    gj = display.GeoJSON(
        data={
            "type": "Feature",
            "geometry": {"type": "Point", "coordinates": [-81.327, 296.038]},
            "properties": {"name": "Inca City"},
        },
        url_template="http://s3-eu-west-1.amazonaws.com/whereonmars.cartodb.net/{basemap_id}/{z}/{x}/{y}.png",
        layer_options={
            "basemap_id": "celestia_mars-shaded-16k_global",
            "attribution": "Celestia/praesepe",
            "minZoom": 0,
            "maxZoom": 18,
        },
    )
    assert "<IPython.core.display.GeoJSON object>" == str(gj)


def test_retina_png():
    here = os.path.dirname(__file__)
    img = display.Image(os.path.join(here, "2x2.png"), retina=True)
    assert img.height == 1
    assert img.width == 1
    data, md = img._repr_png_()
    assert md["width"] == 1
    assert md["height"] == 1


def test_embed_svg_url():
    import gzip
    from io import BytesIO

    svg_data = b'<svg><circle x="0" y="0" r="1"/></svg>'
    url = "http://test.com/circle.svg"

    gzip_svg = BytesIO()
    with gzip.open(gzip_svg, "wb") as fp:
        fp.write(svg_data)
    gzip_svg = gzip_svg.getvalue()

    def mocked_urlopen(*args, **kwargs):
        class MockResponse:
            def __init__(self, svg):
                self._svg_data = svg
                self.headers = {"content-type": "image/svg+xml"}

            def read(self):
                return self._svg_data

        if args[0] == url:
            return MockResponse(svg_data)
        elif args[0] == url + "z":
            ret = MockResponse(gzip_svg)
            ret.headers["content-encoding"] = "gzip"
            return ret
        return MockResponse(None)

    with mock.patch("urllib.request.urlopen", side_effect=mocked_urlopen):
        svg = display.SVG(url=url)
        assert svg._repr_svg_().startswith("<svg") is True
        svg = display.SVG(url=url + "z")
        assert svg._repr_svg_().startswith("<svg") is True


def test_retina_jpeg():
    here = os.path.dirname(__file__)
    img = display.Image(os.path.join(here, "2x2.jpg"), retina=True)
    assert img.height == 1
    assert img.width == 1
    data, md = img._repr_jpeg_()
    assert md["width"] == 1
    assert md["height"] == 1


def test_base64image():
    display.Image(
        "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAAACnej3aAAAAAWJLR0QAiAUdSAAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB94BCRQnOqNu0b4AAAAKSURBVAjXY2AAAAACAAHiIbwzAAAAAElFTkSuQmCC"
    )


def test_image_filename_defaults():
    """test format constraint, and validity of jpeg and png"""
    tpath = ipath.get_ipython_package_dir()
    pytest.raises(
        ValueError,
        display.Image,
        filename=os.path.join(tpath, "testing/tests/badformat.zip"),
        embed=True,
    )
    pytest.raises(ValueError, display.Image)
    pytest.raises(
        ValueError,
        display.Image,
        data="this is not an image",
        format="badformat",
        embed=True,
    )


def _get_inline_config():
    from matplotlib_inline.config import InlineBackend

    return InlineBackend.instance()


@dec.skip_without("matplotlib")
def test_set_matplotlib_close():
    from matplotlib_inline.backend_inline import set_matplotlib_close

    cfg = _get_inline_config()
    cfg.close_figures = False

    set_matplotlib_close()
    assert cfg.close_figures
    set_matplotlib_close(False)
    assert not cfg.close_figures


_fmt_mime_map = {
    "png": "image/png",
    "jpeg": "image/jpeg",
    "pdf": "application/pdf",
    "retina": "image/png",
    "svg": "image/svg+xml",
}


@dec.skip_without("matplotlib")
def test_set_matplotlib_formats():
    from matplotlib.figure import Figure
    from matplotlib_inline.backend_inline import set_matplotlib_formats

    formatters = get_ipython().display_formatter.formatters
    for formats in [
        ("png",),
        ("pdf", "svg"),
        ("jpeg", "retina", "png"),
        (),
    ]:
        active_mimes = {_fmt_mime_map[fmt] for fmt in formats}
        set_matplotlib_formats(*formats)
        for mime, f in formatters.items():
            if mime in active_mimes:
                assert Figure in f
            else:
                assert Figure not in f


@dec.skip_without("matplotlib")
def test_set_matplotlib_formats_kwargs():
    from matplotlib.figure import Figure
    from matplotlib_inline.backend_inline import set_matplotlib_formats

    ip = get_ipython()
    cfg = _get_inline_config()
    cfg.print_figure_kwargs.update(dict(foo="bar"))
    kwargs = dict(dpi=150)
    try:
        set_matplotlib_formats("png", **kwargs)
        formatter = ip.display_formatter.formatters["image/png"]
        f = formatter.lookup_by_type(Figure)
        formatter_kwargs = f.keywords
        expected = kwargs
        expected["base64"] = True
        expected["fmt"] = "png"
        expected.update(cfg.print_figure_kwargs)
        assert formatter_kwargs == expected
    finally:
        cfg.print_figure_kwargs.clear()


@dec.skip_without("matplotlib")
def test_matplotlib_positioning():
    _ip = get_ipython()

    prev_active_types = _ip.display_formatter.active_types.copy()
    prev_execution_count = _ip.execution_count
    prev_user_ns_underscore = _ip.user_ns.get("_", None)

    _ip.history_manager.reset()
    _ip.display_formatter.active_types = ["text/plain", "image/png"]

    _ip.run_cell("import matplotlib")
    prev_mpl_backend = _ip.run_cell("matplotlib.get_backend()").result

    try:
        _ip.run_line_magic("matplotlib", "inline")
        _ip.execution_count = 1
        _ip.run_cell("'no plot here'", store_history=True)

        # Cell 2: No manual flush
        _ip.run_cell(
            "import matplotlib.pyplot as plt;plt.plot([0, 1])", store_history=True
        )

        _ip.run_cell("'no plot here'", store_history=True)

        # Cell 4: Manual flush
        _ip.run_cell("plt.plot([1, 0])\nplt.show()", store_history=True)

        _ip.run_cell("'no plot here'", store_history=True)

        outputs = _ip.history_manager.outputs

        # Only cells 2 and 4 should have plots
        for cell_num in [1, 3, 5]:
            assert not any(
                "image/png" in out.bundle for out in outputs.get(cell_num, [])
            ), f"Cell {cell_num} should not have plot"

        cell_2_has_plot = any("image/png" in out.bundle for out in outputs.get(2, []))
        cell_4_has_plot = any("image/png" in out.bundle for out in outputs.get(4, []))

        assert cell_2_has_plot, "Cell 2 should have plot (auto-flush)"
        assert cell_4_has_plot, "Cell 4 should have plot (manual flush)"

    finally:
        _ip.run_cell("plt.close('all')")
        _ip.run_line_magic("matplotlib", prev_mpl_backend)
        _ip.history_manager.reset()
        _ip.display_formatter.active_types = prev_active_types
        _ip.displayhook.flush()

        _ip.execution_count = prev_execution_count
        if prev_user_ns_underscore is not None:
            _ip.user_ns["_"] = prev_user_ns_underscore
        elif "_" in _ip.user_ns:
            del _ip.user_ns["_"]


def test_display_available():
    """
    Test that display is available without import

    We don't really care if it's in builtin or anything else, but it should
    always be available.
    """
    ip = get_ipython()
    with AssertNotPrints("NameError"):
        ip.run_cell("display")
    try:
        ip.run_cell("del display")
    except NameError:
        pass  # it's ok, it might be in builtins
    # even if deleted it should be back
    with AssertNotPrints("NameError"):
        ip.run_cell("display")


def test_textdisplayobj_pretty_repr():
    p = display.Pretty("This is a simple test")
    assert repr(p) == "<IPython.core.display.Pretty object>"
    assert p.data == "This is a simple test"

    p._show_mem_addr = True
    assert repr(p) == object.__repr__(p)


def test_displayobject_repr():
    h = display.HTML("<br />")
    assert repr(h) == "<IPython.core.display.HTML object>"
    h._show_mem_addr = True
    assert repr(h) == object.__repr__(h)
    h._show_mem_addr = False
    assert repr(h) == "<IPython.core.display.HTML object>"

    j = display.Javascript("")
    assert repr(j) == "<IPython.core.display.Javascript object>"
    j._show_mem_addr = True
    assert repr(j) == object.__repr__(j)
    j._show_mem_addr = False
    assert repr(j) == "<IPython.core.display.Javascript object>"


@mock.patch("warnings.warn")
def test_encourage_iframe_over_html(m_warn):
    display.HTML()
    m_warn.assert_not_called()

    display.HTML("<br />")
    m_warn.assert_not_called()

    display.HTML(
        '<html><p>Lots of content here</p><iframe src="http://a.com"></iframe>'
    )
    m_warn.assert_not_called()

    display.HTML('<iframe src="http://a.com"></iframe>')
    m_warn.assert_called_with("Consider using IPython.display.IFrame instead")

    m_warn.reset_mock()
    display.HTML('<IFRAME SRC="http://a.com"></IFRAME>')
    m_warn.assert_called_with("Consider using IPython.display.IFrame instead")


def test_progress():
    p = display.ProgressBar(10)
    assert "0/10" in repr(p)
    p.html_width = "100%"
    p.progress = 5
    assert (
        p._repr_html_() == "<progress style='width:100%' max='10' value='5'></progress>"
    )


def test_progress_iter():
    with capture_output(display=False) as captured:
        for i in display.ProgressBar(5):
            out = captured.stdout
            assert "{0}/5".format(i) in out
    out = captured.stdout
    assert "5/5" in out


def test_json():
    d = {"a": 5}
    lis = [d]
    metadata = [
        {"expanded": False, "root": "root"},
        {"expanded": True, "root": "root"},
        {"expanded": False, "root": "custom"},
        {"expanded": True, "root": "custom"},
    ]
    json_objs = [
        display.JSON(d),
        display.JSON(d, expanded=True),
        display.JSON(d, root="custom"),
        display.JSON(d, expanded=True, root="custom"),
    ]
    for j, md in zip(json_objs, metadata):
        assert j._repr_json_() == (d, md)

    with warnings.catch_warnings(record=True) as w:
        warnings.simplefilter("always")
        j = display.JSON(json.dumps(d))
        assert len(w) == 1
        assert j._repr_json_() == (d, metadata[0])

    json_objs = [
        display.JSON(lis),
        display.JSON(lis, expanded=True),
        display.JSON(lis, root="custom"),
        display.JSON(lis, expanded=True, root="custom"),
    ]
    for j, md in zip(json_objs, metadata):
        assert j._repr_json_() == (lis, md)

    with warnings.catch_warnings(record=True) as w:
        warnings.simplefilter("always")
        j = display.JSON(json.dumps(lis))
        assert len(w) == 1
        assert j._repr_json_() == (lis, metadata[0])


def test_video_embedding():
    """use a tempfile, with dummy-data, to ensure that video embedding doesn't crash"""
    v = display.Video("http://ignored")
    assert not v.embed
    html = v._repr_html_()
    assert 'src="data:' not in html
    assert 'src="http://ignored"' in html

    with pytest.raises(ValueError):
        v = display.Video(b"abc")

    with NamedFileInTemporaryDirectory("test.mp4", "wb") as f:
        f.write(b"abc")
        f.close()

        v = display.Video(f.name)
        assert not v.embed
        html = v._repr_html_()
        assert 'src="data:' not in html

        v = display.Video(f.name, embed=True)
        html = v._repr_html_()
        assert 'src="data:video/mp4;base64,YWJj"' in html

        v = display.Video(f.name, embed=True, mimetype="video/other")
        html = v._repr_html_()
        assert 'src="data:video/other;base64,YWJj"' in html

        v = display.Video(b"abc", embed=True, mimetype="video/mp4")
        html = v._repr_html_()
        assert 'src="data:video/mp4;base64,YWJj"' in html

        v = display.Video("YWJj", embed=True, mimetype="video/xyz")
        html = v._repr_html_()
        assert 'src="data:video/xyz;base64,YWJj"' in html


def test_html_metadata():
    s = "<h1>Test</h1>"
    h = display.HTML(s, metadata={"isolated": True})
    assert h._repr_html_() == (s, {"isolated": True})


def test_display_id():
    ip = get_ipython()
    with mock.patch.object(ip.display_pub, "publish") as pub:
        handle = display.display("x")
        assert handle is None
        handle = display.display("y", display_id="secret")
        assert isinstance(handle, display.DisplayHandle)
        handle2 = display.display("z", display_id=True)
        assert isinstance(handle2, display.DisplayHandle)
    assert handle.display_id != handle2.display_id

    assert pub.call_count == 3
    args, kwargs = pub.call_args_list[0]
    assert args == ()
    assert kwargs == {
        "data": {"text/plain": repr("x")},
        "metadata": {},
    }
    args, kwargs = pub.call_args_list[1]
    assert args == ()
    assert kwargs == {
        "data": {"text/plain": repr("y")},
        "metadata": {},
        "transient": {
            "display_id": handle.display_id,
        },
    }
    args, kwargs = pub.call_args_list[2]
    assert args == ()
    assert kwargs == {
        "data": {"text/plain": repr("z")},
        "metadata": {},
        "transient": {
            "display_id": handle2.display_id,
        },
    }


def test_update_display():
    ip = get_ipython()
    with mock.patch.object(ip.display_pub, "publish") as pub:
        with pytest.raises(TypeError):
            display.update_display("x")
        display.update_display("x", display_id="1")
        display.update_display("y", display_id="2")
    args, kwargs = pub.call_args_list[0]
    assert args == ()
    assert kwargs == {
        "data": {"text/plain": repr("x")},
        "metadata": {},
        "transient": {
            "display_id": "1",
        },
        "update": True,
    }
    args, kwargs = pub.call_args_list[1]
    assert args == ()
    assert kwargs == {
        "data": {"text/plain": repr("y")},
        "metadata": {},
        "transient": {
            "display_id": "2",
        },
        "update": True,
    }


def test_display_handle():
    ip = get_ipython()
    handle = display.DisplayHandle()
    assert isinstance(handle.display_id, str)
    handle = display.DisplayHandle("my-id")
    assert handle.display_id == "my-id"
    with mock.patch.object(ip.display_pub, "publish") as pub:
        handle.display("x")
        handle.update("y")

    args, kwargs = pub.call_args_list[0]
    assert args == ()
    assert kwargs == {
        "data": {"text/plain": repr("x")},
        "metadata": {},
        "transient": {
            "display_id": handle.display_id,
        },
    }
    args, kwargs = pub.call_args_list[1]
    assert args == ()
    assert kwargs == {
        "data": {"text/plain": repr("y")},
        "metadata": {},
        "transient": {
            "display_id": handle.display_id,
        },
        "update": True,
    }


def test_image_alt_tag():
    """Simple test for display.Image(args, alt=x,)"""
    thisurl = "http://example.com/image.png"
    img = display.Image(url=thisurl, alt="an image")
    assert '<img src="%s" alt="an image"/>' % (thisurl) == img._repr_html_()
    img = display.Image(url=thisurl, unconfined=True, alt="an image")
    assert (
        '<img src="%s" class="unconfined" alt="an image"/>' % (thisurl)
        == img._repr_html_()
    )
    img = display.Image(url=thisurl, alt='>"& <')
    assert '<img src="%s" alt="&gt;&quot;&amp; &lt;"/>' % (thisurl) == img._repr_html_()

    img = display.Image(url=thisurl, metadata={"alt": "an image"})
    assert img.alt == "an image"
    here = os.path.dirname(__file__)
    img = display.Image(os.path.join(here, "2x2.png"), alt="an image")
    assert img.alt == "an image"
    _, md = img._repr_png_()
    assert md["alt"] == "an image"


def test_image_bad_filename_raises_proper_exception():
    with pytest.raises(FileNotFoundError):
        display.Image("/this/file/does/not/exist/")._repr_png_()
