# -*- coding: utf-8 -*-
#
#
# TheVirtualBrain-Framework Package. This package holds all Data Management, and 
# Web-UI helpful to run brain-simulations. To use it, you also need do download
# TheVirtualBrain-Scientific Package (for simulators). See content of the
# documentation-folder for more details. See also http://www.thevirtualbrain.org
#
# (c) 2012-2020, Baycrest Centre for Geriatric Care ("Baycrest") and others
#
# This program 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.
# This program 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 this
# program.  If not, see <http://www.gnu.org/licenses/>.
#
#
#   CITATION:
# When using The Virtual Brain for scientific publications, please cite it as follows:
#
#   Paula Sanz Leon, Stuart A. Knock, M. Marmaduke Woodman, Lia Domide,
#   Jochen Mersmann, Anthony R. McIntosh, Viktor Jirsa (2013)
#       The Virtual Brain: a simulator of primate brain network dynamics.
#   Frontiers in Neuroinformatics (7:10. doi: 10.3389/fninf.2013.00010)
#
#

"""
.. Ionel Ortelecan <ionel.ortelecan@codemart.ro>
"""

import uuid

from tvb.adapters.datatypes.db.connectivity import ConnectivityIndex
from tvb.adapters.datatypes.db.patterns import StimuliRegionIndex, StimuliSurfaceIndex
from tvb.adapters.datatypes.db.surface import SurfaceIndex
from tvb.adapters.simulator.equation_forms import get_form_for_equation
from tvb.adapters.simulator.subforms_mapping import DOUBLE_GAUSSIAN_EQUATION, SIGMOID_EQUATION
from tvb.adapters.simulator.subforms_mapping import get_ui_name_to_equation_dict, GAUSSIAN_EQUATION
from tvb.basic.neotraits.api import Attr
from tvb.core.adapters.abcadapter import ABCAdapterForm, AdapterLaunchModeEnum, ABCAdapter
from tvb.core.entities.filters.chain import FilterChain
from tvb.core.neocom import h5
from tvb.core.neotraits.forms import FormField, TraitDataTypeSelectField, SelectField, StrField
from tvb.core.neotraits.view_model import ViewModel, DataTypeGidAttr, Str
from tvb.datatypes.connectivity import Connectivity
from tvb.datatypes.equations import Sigmoid, PulseTrain, TemporalApplicableEquation, FiniteSupportEquation
from tvb.datatypes.patterns import StimuliSurface, StimuliRegion
from tvb.datatypes.surfaces import CorticalSurface, CORTICAL


class StimulusSurfaceSelectorForm(ABCAdapterForm):

    def __init__(self):
        super(StimulusSurfaceSelectorForm, self).__init__()
        traited_attr = Attr(StimuliSurfaceIndex, label='Load Surface Stimulus', required=False)
        self.surface_stimulus = TraitDataTypeSelectField(traited_attr, name='existentEntitiesSelect')
        self.display_name = StrField(SurfaceStimulusCreatorModel.display_name, name='display_name')

    def get_rendering_dict(self):
        return {'adapter_form': self, 'legend': 'Loaded stimulus'}


class SurfaceStimulusCreatorModel(ViewModel, StimuliSurface):
    spatial = Attr(field_type=FiniteSupportEquation, label="Spatial Equation", default=Sigmoid())
    temporal = Attr(field_type=TemporalApplicableEquation, label="Temporal Equation", default=PulseTrain())

    surface = DataTypeGidAttr(
        linked_datatype=CorticalSurface,
        label=StimuliSurface.surface.label
    )

    display_name = Str(
        label='Display name',
        required=False
    )


class SurfaceStimulusCreatorForm(ABCAdapterForm):
    NAME_SPATIAL_PARAMS_DIV = 'spatial_params'
    NAME_TEMPORAL_PARAMS_DIV = 'temporal_params'
    default_spatial = Sigmoid
    default_temporal = PulseTrain
    choices_temporal = get_ui_name_to_equation_dict()
    choices_spatial = {GAUSSIAN_EQUATION: choices_temporal.get(GAUSSIAN_EQUATION),
                       DOUBLE_GAUSSIAN_EQUATION: choices_temporal.get(DOUBLE_GAUSSIAN_EQUATION),
                       SIGMOID_EQUATION: choices_temporal.get(SIGMOID_EQUATION)}

    def __init__(self):
        super(SurfaceStimulusCreatorForm, self).__init__()

        self.surface = TraitDataTypeSelectField(SurfaceStimulusCreatorModel.surface, name='surface',
                                                conditions=self.get_filters())
        self.spatial = SelectField(SurfaceStimulusCreatorModel.spatial, name='spatial', choices=self.choices_spatial,
                                   subform=get_form_for_equation(self.default_spatial))
        self.temporal = SelectField(SurfaceStimulusCreatorModel.temporal, name='temporal',
                                    choices=self.choices_temporal,
                                    subform=get_form_for_equation(self.default_temporal))

    @staticmethod
    def get_view_model():
        return SurfaceStimulusCreatorModel

    @staticmethod
    def get_required_datatype():
        return SurfaceIndex

    @staticmethod
    def get_input_name():
        return 'surface'

    @staticmethod
    def get_filters():
        return FilterChain(fields=[FilterChain.datatype + '.surface_type'], operations=["=="],
                           values=[CORTICAL])

    def fill_from_trait(self, trait):
        self.surface.data = trait.surface.hex
        self.spatial.data = type(trait.spatial)
        self.temporal.data = type(trait.temporal)
        self.temporal.subform_field = FormField(get_form_for_equation(type(trait.temporal)),
                                                self.NAME_TEMPORAL_PARAMS_DIV)
        self.temporal.subform_field.form.fill_from_trait(trait.temporal)
        self.spatial.subform_field = FormField(get_form_for_equation(type(trait.spatial)), self.NAME_SPATIAL_PARAMS_DIV)
        self.spatial.subform_field.form.fill_from_trait(trait.spatial)

    def get_rendering_dict(self):
        return {'adapter_form': self, 'next_action': 'form_spatial_surface_stimulus_equations',
                'spatial_params_div': self.NAME_SPATIAL_PARAMS_DIV,
                'temporal_params_div': self.NAME_TEMPORAL_PARAMS_DIV, 'legend': 'Stimulus interface'}


class SurfaceStimulusCreator(ABCAdapter):
    """
    The purpose of this adapter is to create a StimuliSurface.
    """
    KEY_SURFACE = 'surface'
    KEY_SPATIAL = 'spatial'
    KEY_TEMPORAL = 'temporal'
    KEY_FOCAL_POINTS_TRIANGLES = 'focal_points_triangles'
    launch_mode = AdapterLaunchModeEnum.SYNC_SAME_MEM

    def get_form_class(self):
        return SurfaceStimulusCreatorForm

    def get_output(self):
        """
        Describes the outputs of the launch method.
        """
        return [StimuliSurfaceIndex]

    def prepare_stimuli_surface_from_view_model(self, view_model, load_full_surface=False):
        # type: (SurfaceStimulusCreatorModel, bool) -> StimuliSurface
        stimuli_surface = StimuliSurface()

        stimuli_surface.focal_points_triangles = view_model.focal_points_triangles
        stimuli_surface.spatial = view_model.spatial
        stimuli_surface.temporal = view_model.temporal

        if load_full_surface:
            stimuli_surface.surface = self.load_traited_by_gid(view_model.surface)
        else:
            stimuli_surface.surface = CorticalSurface()
            stimuli_surface.gid = view_model.surface
            # We need to load surface triangles on stimuli because focal_points_surface property needs to acces them
            with h5.h5_file_for_gid(view_model.surface) as surface_h5:
                stimuli_surface.surface.triangles = surface_h5.triangles.load()

        return stimuli_surface

    def launch(self, view_model):
        # type: (SurfaceStimulusCreatorModel) -> [StimuliSurfaceIndex]
        """
        Used for creating a `StimuliSurface` instance
        """
        self.generic_attributes.user_tag_1 = view_model.display_name
        stimuli_surface = self.prepare_stimuli_surface_from_view_model(view_model, view_model.surface)
        stimuli_surface_index = h5.store_complete(stimuli_surface, self.storage_path)
        return stimuli_surface_index

    def get_required_memory_size(self, view_model):
        # type: (SurfaceStimulusCreatorModel) -> int
        """
        Return the required memory to run this algorithm.
        """
        return -1

    def get_required_disk_size(self, view_model):
        # type: (SurfaceStimulusCreatorModel) -> int
        """
        Returns the required disk size to be able to run the adapter. (in kB)
        """
        return 0


class StimulusRegionSelectorForm(ABCAdapterForm):

    def __init__(self):
        super(StimulusRegionSelectorForm, self).__init__()
        traited_attr = Attr(StimuliRegionIndex, label='Load Region Stimulus', required=False)
        self.region_stimulus = TraitDataTypeSelectField(traited_attr, name='existentEntitiesSelect')
        self.display_name = StrField(RegionStimulusCreatorModel.display_name, name='display_name')

    def get_rendering_dict(self):
        return {'adapter_form': self, 'legend': 'Loaded stimulus'}


class RegionStimulusCreatorModel(ViewModel, StimuliRegion):
    temporal = Attr(field_type=TemporalApplicableEquation, label="Temporal Equation", default=PulseTrain())

    connectivity = DataTypeGidAttr(
        field_type=uuid.UUID,
        linked_datatype=Connectivity,
        label="Connectivity"
    )

    display_name = Str(
        label='Display name',
        required=False
    )


class RegionStimulusCreatorForm(ABCAdapterForm):
    NAME_TEMPORAL_PARAMS_DIV = 'temporal_params'
    default_temporal = PulseTrain
    choices = get_ui_name_to_equation_dict()

    def __init__(self):
        super(RegionStimulusCreatorForm, self).__init__()
        self.connectivity = TraitDataTypeSelectField(RegionStimulusCreatorModel.connectivity, name='connectivity')
        self.temporal = SelectField(RegionStimulusCreatorModel.temporal, name='temporal', choices=self.choices,
                                    subform=get_form_for_equation(self.default_temporal))

    @staticmethod
    def get_view_model():
        return RegionStimulusCreatorModel

    @staticmethod
    def get_filters():
        return None

    @staticmethod
    def get_input_name():
        return 'connectivity'

    @staticmethod
    def get_required_datatype():
        return ConnectivityIndex

    def fill_from_trait(self, trait):
        # type: (RegionStimulusCreatorModel) -> None
        self.connectivity.data = trait.connectivity.hex
        self.temporal.data = type(trait.temporal)
        self.temporal.subform_field = FormField(get_form_for_equation(type(trait.temporal)),
                                                self.NAME_TEMPORAL_PARAMS_DIV)
        self.temporal.subform_field.form.fill_from_trait(trait.temporal)

    def get_rendering_dict(self):
        return {'adapter_form': self, 'next_action': 'form_spatial_model_param_equations',
                'temporal_params_div': self.NAME_TEMPORAL_PARAMS_DIV, 'legend': 'Stimulus interface'}


class RegionStimulusCreator(ABCAdapter):
    """
    The purpose of this adapter is to create a StimuliRegion.
    """
    launch_mode = AdapterLaunchModeEnum.SYNC_SAME_MEM

    def get_form_class(self):
        return RegionStimulusCreatorForm

    def get_output(self):
        """
        Describes the outputs of the launch method.
        """
        return [StimuliRegionIndex]

    def launch(self, view_model):
        # type: (RegionStimulusCreatorModel) -> [StimuliRegionIndex]
        """
        Used for creating a `StimuliRegion` instance
        """
        stimuli_region = StimuliRegion()
        stimuli_region.connectivity = Connectivity()
        stimuli_region.connectivity.gid = view_model.connectivity
        stimuli_region.weight = view_model.weight
        stimuli_region.temporal = view_model.temporal
        self.generic_attributes.user_tag_1 = view_model.display_name

        stimuli_region_idx = h5.store_complete(stimuli_region, self.storage_path)
        return stimuli_region_idx

    def get_required_disk_size(self, view_model):
        # type: (RegionStimulusCreatorModel) -> int
        """
        Returns the required disk size to be able to run the adapter. (in kB)
        """
        return 0

    def get_required_memory_size(self, view_model):
        # type: (RegionStimulusCreatorModel) -> int
        """
        Return the required memory to run this algorithm.
        """
        return -1
