# -*- coding: utf-8 -*-
# Copyright 2007-2021 The HyperSpy developers
#
# This file is part of  HyperSpy.
#
#  HyperSpy is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
#  HyperSpy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with  HyperSpy.  If not, see <http://www.gnu.org/licenses/>.

import numpy as np
import pytest

from hyperspy.misc.utils import (DictionaryTreeBrowser, check_long_string,
                                 replace_html_symbols,
                                 nested_dictionary_merge)
from hyperspy.signal import BaseSignal


@pytest.fixture(params=[True, False], ids=['lazy', 'not_lazy'])
def tree(request):
    lazy = request.param
    tree = DictionaryTreeBrowser(
        {
            "Node1": {"leaf11": 11,
                      "Node11": {"leaf111": 111},
                      },
            "Node2": {"leaf21": 21,
                      "Node21": {"leaf211": 211},
                      },
        }, lazy=lazy)
    return tree


class TestDictionaryBrowser:

    def test_add_dictionary(self, tree):
        tree.add_dictionary({
            "Node1": {"leaf12": 12,
                      "Node11": {"leaf111": 222,
                                 "Node111": {"leaf1111": 1111}, },
                      },
            "Node3": {
                "leaf31": 31},
        })
        assert (
            {"Node1": {"leaf11": 11,
                       "leaf12": 12,
                       "Node11": {"leaf111": 222,
                                  "Node111": {
                                      "leaf1111": 1111},
                                  },
                       },
             "Node2": {"leaf21": 21,
                       "Node21": {"leaf211": 211},
                       },
             "Node3": {"leaf31": 31},
             } == tree.as_dictionary())

    def test_deepcopy(self, tree):
        a = tree.deepcopy()
        assert a.as_dictionary() == tree.as_dictionary()

    def test_add_dictionary_space_key(self, tree):
        tree.add_dictionary({'a key with a space': 'a value'})
        assert tree.a_key_with_a_space == 'a value'
        # All lazy attribute should have been processed
        assert len(tree._lazy_attributes) == 0

    def test_add_signal_in_dictionary(self, tree):
        s = BaseSignal([1., 2, 3])
        s.axes_manager[0].name = 'x'
        s.axes_manager[0].units = 'ly'
        tree.add_dictionary({"_sig_signal name": s._to_dictionary()})
        assert isinstance(tree.signal_name, BaseSignal)
        np.testing.assert_array_equal(tree.signal_name.data, s.data)
        assert (tree.signal_name.metadata.as_dictionary() ==
                s.metadata.as_dictionary())
        assert (tree.signal_name.axes_manager._get_axes_dicts() ==
                s.axes_manager._get_axes_dicts())

    def test_signal_to_dictionary(self, tree):
        s = BaseSignal([1., 2, 3])
        s.axes_manager[0].name = 'x'
        s.axes_manager[0].units = 'ly'
        tree.set_item('Some name', s)
        d = tree.as_dictionary()
        np.testing.assert_array_equal(d['_sig_Some name']['data'], s.data)
        d['_sig_Some name']['data'] = 0
        d2 = {
            "Node1": {
                "leaf11": 11,
                "Node11": {
                    "leaf111": 111},
            },
            "Node2": {
                "leaf21": 21,
                "Node21": {
                    "leaf211": 211},
            },
            "_sig_Some name": {
                'attributes': {'_lazy': False},
                'axes': [
                    {
                        'name': 'x',
                        'navigate': False,
                                'offset': 0.0,
                                'scale': 1.0,
                                'size': 3,
                                'units': 'ly'}],
                'data': 0,
                'learning_results': {},
                'metadata': {
                    'General': {
                        'title': ''},
                    'Signal': {
                        'binned': False,
                        'signal_type': ''},
                    '_HyperSpy': {
                        'Folding': {
                            'original_axes_manager': None,
                            'original_shape': None,
                            'unfolded': False,
                            'signal_unfolded': False}}},
                'original_metadata': {},
                'tmp_parameters': {}}}
        assert d2 == d

    def _test_date_time(self, tree, dt_str='now'):
        dt0 = np.datetime64(dt_str)
        data_str, time_str = np.datetime_as_string(dt0).split('T')
        tree.add_node("General")
        tree.General.date = data_str
        tree.General.time = time_str

        dt1 = np.datetime64(f'{tree.General.date}T{tree.General.time}')

        np.testing.assert_equal(dt0, dt1)
        return dt1

    def test_date_time_now(self, tree):
        # not really a test, more a demo to show how to set and use date and
        # time in the DictionaryBrowser
        self._test_date_time(tree)

    def test_date_time_nanosecond_precision(self, tree):
        # not really a test, more a demo to show how to set and use date and
        # time in the DictionaryBrowser
        dt_str = '2016-08-05T10:13:15.450580'
        self._test_date_time(tree, dt_str)

    def test_has_item(self, tree):
        # Check that it finds all actual items:
        assert tree.has_item('Node1')
        assert tree.has_item('Node1.leaf11')
        assert tree.has_item('Node1.Node11')
        assert tree.has_item('Node1.Node11.leaf111')
        assert tree.has_item('Node2')
        assert tree.has_item('Node2.leaf21')
        assert tree.has_item('Node2.Node21')
        assert tree.has_item('Node2.Node21.leaf211')

        # Check that it doesn't find non-existant ones
        assert not tree.has_item('Node3')
        assert not tree.has_item('General')
        assert not tree.has_item('Node1.leaf21')
        assert not tree.has_item('')
        assert not tree.has_item('.')
        assert not tree.has_item('..Node1')

    def test_get_item(self, tree):
        assert tree['Node1']['leaf11'] == 11

        # Check that it gets all leaf nodes:
        assert tree.get_item('Node1.leaf11') == 11
        assert tree.get_item('Node1.Node11.leaf111') == 111
        assert tree.get_item('Node2.leaf21') == 21
        assert tree.get_item('Node2.Node21.leaf211') == 211

        # Check that it gets all leaf nodes, also with given default:
        assert tree.get_item('Node1.leaf11', 44) == 11
        assert tree.get_item('Node1.Node11.leaf111', 44) == 111
        assert tree.get_item('Node2.leaf21', 44) == 21
        assert tree.get_item('Node2.Node21.leaf211', 44) == 211

        # Check that it returns the default value for various incorrect paths:
        assert tree.get_item('Node1.leaf33', 44) == 44
        assert tree.get_item('Node1.leaf11.leaf111', 44) == 44
        assert tree.get_item('Node1.Node31.leaf311', 44) == 44
        assert tree.get_item('Node1.Node21.leaf311', 44) == 44
        assert tree.get_item('.Node1.Node21.leaf311', 44) == 44

    def test_str(self, tree):
        string = tree.__repr__()
        assert string == "".join(["├── Node1\n",
                                  "│   ├── Node11\n",
                                  "│   │   └── leaf111 = 111\n",
                                  "│   └── leaf11 = 11\n",
                                  "└── Node2\n",
                                  "    ├── Node21\n",
                                  "    │   └── leaf211 = 211\n",
                                  "    └── leaf21 = 21\n"])

    def test_html(self, tree):
        "Test that the method actually runs"
        # We do not have a way to validate html
        # without relying on more dependencies
        tree['<myhtmltag>'] = "5 < 6"
        tree['<mybrokenhtmltag'] = "<hello>"
        tree['mybrokenhtmltag2>'] = ""
        tree._get_html_print_items()

    def test_length(self, tree):
        length = len(tree._lazy_attributes)
        assert len(tree) == 2
        assert len(tree._lazy_attributes) == length

    def test_iteration(self, tree):
        assert [key for key, value in tree] == ['Node1', 'Node2']

def test_check_long_string():
    max_len = 20
    value = "Hello everyone this is a long string"
    truth, shortened = check_long_string(value, max_len)
    assert truth == False
    assert shortened == 'Hello everyone this is a long string'

    value = "No! It was not a long string! This is a long string!"
    truth, shortened = check_long_string(value, max_len)
    assert truth == True
    assert shortened == 'No! It was not a lon ... is is a long string!'


def test_replace_html_symbols():
    assert '&lt;&gt;&amp' == replace_html_symbols('<>&')
    assert 'no html symbols' == replace_html_symbols('no html symbols')
    assert '&lt;mix&gt;' == replace_html_symbols('<mix>')


def test_add_key_value():
    key = "<foo>"
    value = ">bar<"

    string = """<ul style="margin: 0px; list-style-position: outside;">
        <li style='margin-left:1em; padding-left: 0.5em'>{} = {}</li></ul>
        """.format(replace_html_symbols(key), replace_html_symbols(value))

    assert string == '<ul style="margin: 0px; list-style-position: outside;">\n        <li style=\'margin-left:1em; padding-left: 0.5em\'>&lt;foo&gt; = &gt;bar&lt;</li></ul>\n        '


def test_nested_dictionary_merge():
    a = {"Node1": {"leaf11": 11,
                   "Node11": {"leaf111": 111}, },
         "Node2": {"leaf21": 21,
                   "Node21": {"leaf211": 211}, },
         }
    b = {"Node1": {"leaf12": 12,
                   "Node11": {"leaf111": 222,
                              "Node111": {"leaf1111": 1111}, }, },
         "Node3": {"leaf31": 31},
         }
    nested_dictionary_merge(a, b)
    merged_dict = {"Node1": {"leaf11": 11,
                             "leaf12": 12,
                             "Node11": {"leaf111": 222,
                                        "Node111": {"leaf1111": 1111}, }, },
                   "Node2": {"leaf21": 21,
                             "Node21": {"leaf211": 211}, },
                   "Node3": {"leaf31": 31},
                   }
    assert a == merged_dict

    # Override b to a when conflicts
    a = {"Node1": {"leaf11": 11,
                   "Node11": {"leaf111": 111}, },
         }
    b = {"Node1": {"leaf11": 12,
                   "Node11": {"leaf111": 111}, },
         "Node2": {"leaf21": 21,
                   "Node21": {"leaf211": 211}, },
         }
    nested_dictionary_merge(a, b)
    assert a == b
