# Copyright (C) 2019 South Western Sydney Local Health District,
# University of New South Wales

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

#     http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# This work is derived from:
# https://github.com/AndrewWAlexander/Pinnacle-tar-DICOM
# which is released under the following license:

# Copyright (c) [2017] [Colleen Henschel, Andrew Alexander]

# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:

# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.


import logging
import os
import sys

from .image import convert_image
from .pinn_yaml import pinn_to_dict
from .pinnacle_image import PinnacleImage
from .pinnacle_plan import PinnaclePlan
from .rtdose import convert_dose
from .rtplan import convert_plan
from .rtstruct import convert_struct


class PinnacleExport:
    """Handle Pinnacle data to allow export of DICOM objects

    This class is used to export the various DICOM objects from Pinnacle
    data found within the given path.

    Parameters
    ----------
        path : str
            Path to raw Pinnacle data (directory containing 'Patient' file).
        logger : Logger
            Logger the tool will log to.
    """

    def __init__(self, path, logger=None):

        self._logger = logger  # Logger to use for all logging

        self._path = path  # Path to the root directory of the dataset
        # (containing the Patient file)

        self._patient_info = None  # The patient data read from
        self._plans = None  # Pinnacle plans for this path
        self._images = None  # Images found in image.info

        if not self._logger:
            self._logger = logging.getLogger(__name__)
            self._logger.setLevel(logging.DEBUG)

            if len(self._logger.handlers) == 0:
                ch = logging.StreamHandler(sys.stdout)
                formatter = logging.Formatter(
                    "%(asctime)s - %(levelname)s - %(message)s"
                )
                ch.setFormatter(formatter)
                ch.setLevel(logging.DEBUG)

                self._logger.addHandler(ch)

    @property
    def logger(self):
        """Gets the configured logger.

        Returns
        -------
        logger : Logger
            Logger configured.
        """
        return self._logger

    @property
    def patient_info(self):
        """Gets the patient info.

        Returns
        -------
        patient_info : dict
            Dictionary object containing the data found in the 'Patient'
            file within the raw Pinnacle data.
        """

        if not self._patient_info:
            path_patient = os.path.join(self._path, "Patient")
            self.logger.debug("Reading patient data from: %s", path_patient)
            self._patient_info = pinn_to_dict(path_patient)

            # Set the full patient name
            last_name = self._patient_info["LastName"]
            first_name = self._patient_info["FirstName"]
            middle_name = self._patient_info["MiddleName"]
            self._patient_info["FullName"] = f"{last_name}^{first_name}^{middle_name}^"

            # gets birthday string with numbers and dashes
            dobstr = self._patient_info["DateOfBirth"]
            if "-" in dobstr:
                dob_list = dobstr.split("-")
            elif "/" in dobstr:
                dob_list = dobstr.split("/")
            else:
                dob_list = dobstr.split(" ")

            dob = ""
            for num in dob_list:
                if len(num) == 1:
                    num = f"0{num}"
                dob = f"{dob}{num}"

            self._patient_info["DOB"] = dob

        return self._patient_info

    @property
    def plans(self):
        """Get the plans available.

        Returns
        -------
        plans : list
            List of PinnaclePlan's available within the Pinnacle raw data in the path
            configured.
        """

        # Read patient info to populate patients plans
        if not self._plans:

            self._plans = []
            for plan in self.patient_info["PlanList"]:
                path_plan = os.path.join(self._path, f"Plan_{plan['PlanID']}")
                self._plans.append(PinnaclePlan(self, path_plan, plan))

        return self._plans

    @property
    def images(self):
        """Get the images available.

        Returns
        -------
        images : list
            List of PinnacleImage's available within the Pinnacle raw data in the path
            configured.
        """

        # Read patient info to populate patients images
        if not self._images:
            self._images = []
            for image in self.patient_info["ImageSetList"]:
                pi = PinnacleImage(self, self._path, image)

                # Check that image info exists to ensure the image is really available
                if pi.image_info:
                    self._images.append(pi)

        return self._images

    @staticmethod
    def export_struct(plan, export_path=".", skip_pattern="^$"):
        """Exports the RTSTRUCT DICOM modality.

        Parameters
        ----------
            plan : PinnaclePlan
                The plan for which structures should be exported.
            export_path : str, optional
                The file system path where the DICOM object should be
                exported to.
            skip_pattern : str, optional
                A regular expression specifying which ROIs to skip
                For example: RadCalc|normal_tissue
        """

        # Export Structures for plan
        convert_struct(plan, export_path, skip_pattern)

    @staticmethod
    def export_dose(plan, export_path="."):
        """Exports the RTDOSE DICOM modality.

        Parameters
        ----------
            plan : PinnaclePlan
                The plan for which dose should be exported.
            export_path : str, optional
                The file system path where the DICOM object should be
                exported to.
        """

        # Export dose for plan
        convert_dose(plan, export_path)

    @staticmethod
    def export_plan(plan, export_path="."):
        """Exports the RTPLAN DICOM modality.

        Parameters
        ----------
            plan : PinnaclePlan
                The plan for which dose should be exported.
            export_path : str, optional
                The file system path where the DICOM object should be
                exported to.
        """

        # Export rtplan for plan
        convert_plan(plan, export_path)

    def export_image(self, image=None, series_uid="", export_path="."):
        """Exports an image from the Pinnacle data.

        Parameters
        ----------
            image : PinnacleImage, optional
                The image to export.
            series_uid : str, optional
                The SeriesInstanceUID of the image to export.
            export_path : str, optional
                The file system path where the DICOM objects should be
                exported to.
        """

        if not image and not series_uid:
            self.logger.error("No image to export")

        for im in self.images:
            im_info = im.image_info[0]
            im_suid = im_info["SeriesUID"]
            if len(series_uid) > 0 and im_suid == series_uid:
                convert_image(im, export_path)
                break

        if image:
            convert_image(image, export_path)

    def log_images(self):
        """Outputs all images found in the Pinnacle data to the log."""

        for i in self.images:
            image_header = i.image_header

            if not image_header:
                # If no ImageHeader is available then we can ignore this
                continue

            modality = "?"
            if "modality" in image_header.keys():
                modality = image_header["modality"]

            series_uid = "?"
            if "series_UID" in image_header.keys():
                series_uid = image_header["series_UID"]

            series_date_time = "?"
            if "SeriesDateTime" in image_header.keys():
                series_date_time = image_header["SeriesDateTime"]

            self.logger.info("%s: %s %s", modality, series_uid, series_date_time)

    def log_plan_names(self):
        """Outputs all plans found in the Pinnacle data to the log."""

        for p in self.plans:
            self.logger.info(p.plan_info["PlanName"])

    def log_trial_names(self):
        """Outputs all trials found in the Pinnacle data to the log."""

        for p in self.plans:
            self.logger.info("### %s ###", p.plan_info["PlanName"])
            for t in p.trials:
                self.logger.info("- %s", t["Name"])

    def log_trial_names_in_plan(self, plan):
        """Outputs all trials found within a plan to the log.

        Parameters
        ----------
            plan : PinnaclePlan
                The plan for which to log the trials.
        """

        self.logger.info("### %s ###", plan.plan_info["PlanName"])
        self.logger.info(plan.path)
        for trial in plan.trials:
            self.logger.info("- %s", trial["Name"])
