"""
Model storage.

@author: Gautham Ganapathy
@organization: LEMS (http://neuroml.org/lems/, https://github.com/organizations/LEMS)
@contact: gautham@lisphacker.org
"""

import os
from os.path import dirname
import sys

from lems.base.base import LEMSBase
from lems.base.map import Map
from lems.parser.LEMS import LEMSFileParser
from lems.base.util import merge_maps, merge_lists
from lems.model.component import Constant,ComponentType,Component,FatComponent

from lems.base.errors import ModelError
from lems.base.errors import SimBuildError

from lems.model.fundamental import Dimension,Unit,Include
# from lems.model.component import Constant,ComponentType,Component,FatComponent
from lems.model.simulation import Run,Record,EventRecord,DataDisplay,DataWriter,EventWriter
from lems.model.structure import With,EventConnection,ChildInstance,MultiInstantiate

import xml.dom.minidom as minidom

import logging

class Model(LEMSBase):
    """
    Stores a model.
    """

    logging.basicConfig(level=logging.INFO)

    target_lems_version = '0.7.3'
    branch = 'development'
    schema_location = 'https://raw.githubusercontent.com/LEMS/LEMS/{0}/Schemas/LEMS/LEMS_v{1}.xsd'.format(branch, target_lems_version)
    #schema_location = '/home/padraig/LEMS/Schemas/LEMS/LEMS_v%s.xsd'%target_lems_version
    
    debug = False
    
    def __init__(self, include_includes=True, fail_on_missing_includes=True):
        """
        Constructor.
        """
        
        self.targets = list()
        """ List of targets to be run on startup.
        @type: list(str) """
        
        self.includes = Map()
        """ Dictionary of includes defined in the model.
        @type: dict(str -> lems.model.fundamental.Include """
        
        self.dimensions = Map()
        """ Dictionary of dimensions defined in the model.
        @type: dict(str -> lems.model.fundamental.Dimension """
        
        self.units = Map()
        """ Map of units defined in the model.
        @type: dict(str -> lems.model.fundamental.Unit """
        
        self.component_types = Map()
        """ Map of component types defined in the model.
        @type: dict(str -> lems.model.component.ComponentType) """
        
        self.components = Map()
        """ Map of root components defined in the model.
        @type: dict(str -> lems.model.component.Component) """

        self.fat_components = Map()
        """ Map of root fattened components defined in the model.
        @type: dict(str -> lems.model.component.FatComponent) """

        self.constants = Map()
        """ Map of constants in this component type.
        @type: dict(str -> lems.model.component.Constant) """

        self.include_directories = []
        """ List of include directories to search for included LEMS files.
        @type: list(str) """
        
        self.included_files = []
        """ List of files already included.
        @type: list(str) """
        
        self.description = None
        """ Short description of contents of LEMS file
        @type: str """
        
        self.include_includes = include_includes
        """ Whether to include LEMS definitions in <Include> elements
        @type: boolean """
        
        self.fail_on_missing_includes = fail_on_missing_includes
        """ Whether to raise an Exception when a file in an <Include> element is not found
        @type: boolean """

    def add_target(self, target):
        """
        Adds a simulation target to the model.

        @param target: Name of the component to be added as a
        simulation target.
        @type target: str
        """

        self.targets.append(target)

    def add_include(self, include):
        """
        Adds an include to the model.

        @param include: Include to be added.
        @type include: lems.model.fundamental.Include
        """

        self.includes[include.file] = include

    def add_dimension(self, dimension):
        """
        Adds a dimension to the model.

        @param dimension: Dimension to be added.
        @type dimension: lems.model.fundamental.Dimension
        """

        self.dimensions[dimension.name] = dimension

    def add_unit(self, unit):
        """
        Adds a unit to the model.

        @param unit: Unit to be added.
        @type unit: lems.model.fundamental.Unit
        """

        self.units[unit.symbol] = unit

    def add_component_type(self, component_type):
        """
        Adds a component type to the model.

        @param component_type: Component type to be added.
        @type component_type: lems.model.fundamental.ComponentType
        """
        name = component_type.name

        # To handle colons in names in LEMS
        if ':' in name:
            name = name.replace(':', '_')
            component_type.name = name

        self.component_types[name] = component_type

    def add_component(self, component):
        """
        Adds a component to the model.

        @param component: Component to be added.
        @type component: lems.model.fundamental.Component
        """

        self.components[component.id] = component

    def add_fat_component(self, fat_component):
        """
        Adds a fattened component to the model.

        @param fat_component: Fattened component to be added.
        @type fat_component: lems.model.fundamental.Fat_component
        """

        self.fat_components[fat_component.id] = fat_component

    def add_constant(self, constant):
        """
        Adds a paramter to the model.

        @param constant: Constant to be added.
        @type constant: lems.model.component.Constant
        """

        self.constants[constant.name] = constant

    def add(self, child):
        """
        Adds a typed child object to the model.

        @param child: Child object to be added.
        """

        if isinstance(child, Include):
            self.add_include(child)
        elif isinstance(child, Dimension):
            self.add_dimension(child)
        elif isinstance(child, Unit):
            self.add_unit(child)