#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2019 Satpy developers
#
# This file is part of satpy.
#
# satpy 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.
#
# satpy 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
# satpy.  If not, see <http://www.gnu.org/licenses/>.
"""The HRIT electrol reader tests package."""

import datetime
import unittest
from unittest import mock

import dask.array as da
import numpy as np
import pytest
from xarray import DataArray

from satpy.readers.electrol_hrit import (
    HRITGOMSEpilogueFileHandler,
    HRITGOMSFileHandler,
    HRITGOMSPrologueFileHandler,
    epilogue,
    image_acquisition,
    prologue,
    recarray2dict,
    satellite_status,
)
from satpy.tests.utils import make_dataid

# Simplify some type selections
f64_t = np.float64
i32_t = np.int32
u32_t = np.uint32


class Testrecarray2dict(unittest.TestCase):
    """Test the function that converts numpy record arrays into dicts for use within SatPy."""

    def test_fun(self):
        """Test record array."""
        inner_st = np.dtype([("test_str", "<S20"), ("test_int", "i4")])
        outer_st = np.dtype([("test_sec", inner_st), ("test_flt", "f4")])

        inner_da = np.array([("Testing", 10)], dtype=inner_st)
        outer_da = np.array([(inner_da, 1.45)], dtype=outer_st)

        expected = {"test_sec": {"test_str": np.array([b"Testing"], dtype="<S20"),
                                 "test_int": np.array([10], dtype=np.int32)},
                    "test_flt": np.array([1.45], dtype=np.float32)}
        assert expected == recarray2dict(outer_da)


class TestHRITGOMSProFileHandler(unittest.TestCase):
    """Test the HRIT Prologue FileHandler."""

    # Below are variable definitions used in testing the prologue reader.
    # These values are taken from a typical ELECTRO-L HRIT scene
    test_sat_status = {"TagType": 2,
                       "TagLength": 292,
                       "SatelliteID": 19002,
                       "SatelliteName": b"ELECTRO",
                       "NominalLongitude": 1.3264,
                       "SatelliteCondition": 1,
                       "TimeOffset": 0.}

    test_img_acq = {"TagType": np.repeat(3, 10).astype(u32_t),
                    "TagLength": np.repeat(24, 10).astype(u32_t),
                    "Status": np.repeat(2, 10).astype(u32_t),
                    "StartDelay": np.repeat(9119019, 10).astype(np.int32),
                    "Cel": np.repeat(0., 10)}

    test_calib = np.full((10, 1024), 50, dtype=np.int32)

    test_pro = {"SatelliteStatus": test_sat_status,
                "ImageAcquisition": test_img_acq,
                "ImageCalibration": test_calib}

    @mock.patch("satpy.readers.electrol_hrit.np.fromfile")
    @mock.patch("satpy.readers.electrol_hrit.HRITFileHandler.__init__")
    def test_init(self, new_fh_init, fromfile):
        """Set up the hrit file handler for testing."""
        new_fh_init.return_value.filename = "filename"
        HRITGOMSPrologueFileHandler.filename = "filename"
        HRITGOMSPrologueFileHandler.mda = {"total_header_length": 1}

        # Set up the test data to use within the prologue reader
        tss = np.array([(2, 292, 19002, "ELECTRO", 1.3264, 1, 0.)],
                       dtype=satellite_status)
        tia = np.tile(np.array([(3, 24, 2, 9119019, 0.)],
                      dtype=image_acquisition), (1, 10))
        tc = np.full((10, 1024), 50)
        rtv = np.array([(tss, tia, tc)], dtype=prologue)
        # Pretend to return this when reading a fake prologue
        fromfile.return_value = rtv
        m = mock.mock_open()

        with mock.patch("satpy.readers.electrol_hrit.open",
                        m, create=True) as newopen:
            newopen.return_value.__enter__.return_value.seek.return_value = 1
            test_t = datetime.datetime(2018, 1, 1, 0, 0)
            self.reader = HRITGOMSPrologueFileHandler(
                             "filename", {"platform_shortname": "GOMS2",
                                          "start_time": test_t,
                                          "service": "test_service"},
                             {"filetype": "info"})

        # assertDictEqual doesn't seem to work for dicts containing dicts,
        # so we must compare some items individually
        assert self.test_pro["SatelliteStatus"] == self.reader.prologue["SatelliteStatus"]
        prop = "ImageAcquisition"
        for key in self.reader.prologue[prop]:
            np.testing.assert_array_equal(self.test_pro[prop][key],
                                          self.reader.prologue[prop][key])
        np.testing.assert_array_equal(self.test_pro["ImageCalibration"],
                                      self.reader.prologue["ImageCalibration"])


class TestHRITGOMSEpiFileHandler(unittest.TestCase):
    """Test the HRIT Epilogue FileHandler."""

    @mock.patch("satpy.readers.electrol_hrit.np.fromfile")
    @mock.patch("satpy.readers.electrol_hrit.HRITFileHandler.__init__")
    def test_init(self, new_fh_init, fromfile):
        """Set up the hrit file handler for testing."""
        new_fh_init.return_value.filename = "filename"
        HRITGOMSEpilogueFileHandler.filename = "filename"
        HRITGOMSEpilogueFileHandler.mda = {"total_header_length": 1}

        # Set up the test data to use within the epilogue reader
        rtv = np.ones((1,), dtype=epilogue)
        # Pretend to return this when reading a fake epilogue
        fromfile.return_value = rtv
        m = mock.mock_open()

        with mock.patch("satpy.readers.electrol_hrit.open",
                        m, create=True) as newopen:
            newopen.return_value.__enter__.return_value.seek.return_value = 1
            test_t = datetime.datetime(2018, 1, 1, 0, 0)
            self.reader = HRITGOMSEpilogueFileHandler(
                             "filename", {"platform_shortname": "GOMS2",
                                          "start_time": test_t,
                                          "service": "test_service"},
                             {"filetype": "info"})

            epi = self.reader.epilogue

            # We don't check everything in the epilogue (too many nested dicts)
            # but rather check the epilogue is returned as a dict and that two
            # representative data fields are as we expect.
            assert isinstance(epi, dict)
            np.testing.assert_array_equal(
                epi["RadiometricProcessing"]["RPSummary"]["IsOptic"], np.ones(10))
            np.testing.assert_array_equal(
                epi["GeometricProcessing"]["TimeProcessing"], np.ones(10))


@mock.patch("satpy.readers.electrol_hrit.HRITGOMSFileHandler.__init__", return_value=None)
@mock.patch("satpy.readers.electrol_hrit.HRITFileHandler.get_dataset", return_value={})
class TestHRITGOMSFileHandler(unittest.TestCase):
    """A test of the ELECTRO-L main file handler functions."""

    @mock.patch("satpy.readers.electrol_hrit.HRITGOMSFileHandler.calibrate")
    def test_get_dataset(self, calibrate_mock, *mocks):
        """Test get dataset."""
        key = make_dataid(name="CH1", calibration="counts")
        fake_array = mock.MagicMock()
        fake_array.attrs = dict()
        calibrate_mock.return_value = fake_array
        fh = HRITGOMSFileHandler()
        fh.platform_name = "Electro"
        fh.mda = {"projection_parameters": {"SSP_longitude": 0.0},
                  "orbital_parameters": {"satellite_nominal_longitude": 0.5}}
        info = {"units": "m", "standard_name": "electro", "wavelength": 5.0}
        output = fh.get_dataset(key, info)

        # Check that 'calibrate' is called
        calibrate_mock.assert_called()

        # Check that the correct attributes are returned
        attrs_exp = info.copy()
        attrs_exp.update({"orbital_parameters": {"satellite_nominal_longitude": 0.5,
                                                 "satellite_nominal_latitude": 0.0,
                                                 "projection_longitude": 0.0,
                                                 "projection_latitude": 0.0,
                                                 "projection_altitude": 35785831.00},
                          "platform_name": "Electro",
                          "sensor": "msu-gs"})
        assert dict(output.attrs, **attrs_exp) == output.attrs

    def test_calibrate(self, *mocks):
        """Test calibrate."""
        lut = np.linspace(1e6, 1.6e6, num=1024).astype(np.int32)
        lut = np.tile(lut, (10, 1))
        fh = HRITGOMSFileHandler()
        fh.prologue = {"ImageCalibration": lut}
        fh.chid = 1

        # Set up test input data

        counts = DataArray(da.linspace(1, 1023, 25, chunks=5,
                                       dtype=np.uint16).reshape(5, 5))

        # Test that calibration fails if given a silly mode
        with pytest.raises(NotImplementedError):
            fh.calibrate(counts, "nonsense")

        # Test that 'counts' calibration returns identical values to input
        out = fh.calibrate(counts, "counts")
        assert np.all(out.values == counts.values)

        # Test that 'radiance' calibrates successfully
        out = fh.calibrate(counts, "radiance")
        assert np.allclose(out.values, lut[0, counts] / 1000.0)

        # Test that 'brightness_temperature' calibrates successfully
        out = fh.calibrate(counts, "brightness_temperature")
        assert np.allclose(out.values, lut[0, counts] / 1000.0)

    def test_get_area_def(self, *mocks):
        """Test get_area_def."""
        example_area_ext = (-5566748.0802, -1854249.1809,
                            5570748.6178, 2000.2688)

        fh = HRITGOMSFileHandler()
        fh.mda = {"cfac": 10231753, "lfac": 10231753,
                  "coff": 1392.0, "loff": 0.0, "number_of_lines": 464,
                  "number_of_columns": 2784,
                  "projection_parameters": {"SSP_longitude": 0.0}}
        area = fh.get_area_def(True)

        assert np.allclose(np.array(area.area_extent), np.array(example_area_ext))
