# -*- coding: utf-8 -*-
"""!@package grass.temporal

@brief GRASS Python scripting module (temporal GIS functions)

Temporal GIS related functions to be used in temporal GIS Python library package.

Usage:

>>> import grass.temporal as tgis
>>> tmr = tgis.AbstractTemporalDataset()

(C) 2008-2011 by the GRASS Development Team
This program is free software under the GNU General Public
License (>=v2). Read the file COPYING that comes with GRASS
for details.

@author Soeren Gebbert
"""
from abstract_dataset import *
from datetime_math import *


class AbstractTemporalDataset(AbstractDataset):
    """!This class implements a temporal topology access structure for an abstract dataset

       This object will be set up by temporal topology creation method provided by the 
       TemporallyTopologyBuilder.

       If correctly initialize the calls next() and prev() 
       let the user walk temporally forward and backward in time.

       The following temporal relations with access methods are supported:
       * equal
       * follows
       * precedes
       * overlaps
       * overlapped
       * during (including starts, finishes)
       * contains (including started, finished)
       * starts
       * started
       * finishes
       * finished


       @code:
       # We have build the temporal topology and we know the first map
       start = first
       while start:

           # Print all maps this map temporally contains
           dlist = start.get_contains()
           for map in dlist:
               map.print_info()

           start = start.next()
         @endcode  
        
        Usage:
        
        @code
        
        >>> tmr = AbstractTemporalDataset()
        >>> tmr.print_topology_info()
         +-------------------- Temporal Topology -------------------------------------+
        >>> tmr.print_topology_shell_info()
        
        @endcode
    """

    def __init__(self):
        AbstractDataset.__init__(self)
        self.reset_topology()

    def reset_topology(self):
        """!Reset any information about temporal topology"""
        self._topology = {}
        self._has_topology = False
        
    def get_number_of_relations(self):      
        """! Return a dictionary in which the keys are the relation names and the value
        are the number of relations.
        
        The following relations are available:
        * equal
        * follows
        * precedes
        * overlaps
        * overlapped
        * during (including starts, finishes)
        * contains (including started, finished)
        * starts
        * started
        * finishes
        * finished
        
        To access topological information the temporal topology must be build first
        using the TemporalTopologyBuilder.
        
        @return the dictionary with relations as keys and number as values or None in case the topology wasn't build
        """
        if self._has_topology == False:
            return None
    
        relations = {}
        try:
            relations["equal"] = len(self._topology["EQUAL"]) 
        except:
            relations["equal"] = 0
        try: 
            relations["follows"] = len(self._topology["FOLLOWS"]) 
        except: 
            relations["follows"] = 0
        try: 
            relations["precedes"] = len(self._topology["PRECEDES"])
        except: 
            relations["precedes"] = 0
        try: 
            relations["overlaps"] = len(self._topology["OVERLAPS"])
        except: 
            relations["overlaps"] = 0
        try: 
            relations["overlapped"] = len(self._topology["OVERLAPPED"])
        except: 
            relations["overlapped"] = 0
        try: 
            relations["during"] = len(self._topology["DURING"])
        except: 
            relations["during"] = 0
        try: 
            relations["contains"] = len(self._topology["CONTAINS"])
        except: 
            relations["contains"] = 0
        try: 
            relations["starts"] = len(self._topology["STARTS"])
        except: 
            relations["starts"] = 0
        try:    
            relations["started"] = len(self._topology["STARTED"])
        except: 
            relations["started"] = 0
        try: 
            relations["finishes"] = len(self._topology["FINISHES"])
        except: 
            relations["finishes"] = 0
        try: 
            relations["finished"] = len(self._topology["FINISHED"])
        except: 
            relations["finished"] = 0
            
        return relations

    def set_topology_build_true(self):
        """!Same as name"""
        self._has_topology = True

    def set_topology_build_false(self):
        """!Same as name"""
        self._has_topology = False

    def is_topology_build(self):
        """!Check if the temporal topology was build"""
        return self._has_topology

    def set_next(self, map_):
        """!Set the map that is temporally as closest located after this map.

           Temporally located means that the start time of the "next" map is
           temporally located AFTER the start time of this map, but temporally
           near than other maps of the same dataset.

           @param map_: This object should be of type AbstractMapDataset 
                        or derived classes
        """
        self._topology["NEXT"] = map_

    def set_prev(self, map_):
        """!Set the map that is temporally as closest located before this map.

           Temporally located means that the start time of the "previous" map is
           temporally located BEFORE the start time of this map, but temporally
           near than other maps of the same dataset.

           @param map_: This object should be of type AbstractMapDataset 
                        or derived classes
        """
        self._topology["PREV"] = map_

    def next(self):
        """!Return the map with a start time temporally located after
           the start time of this map, but temporal closer than other maps

           @return A map object or None
        """
        if "NEXT" not in self._topology:
            return None
        return self._topology["NEXT"]

    def prev(self):
        """!Return the map with a start time temporally located before
           the start time of this map, but temporal closer than other maps

           @return A map object or None
        """
        if "PREV" not in self._topology:
            return None
        return self._topology["PREV"]

    def append_equivalent(self, map_):
        """!Append a map with equivalent temporal extent as this map

           @param map_: This object should be of type AbstractMapDataset 
                        or derived classes
        """
        if "EQUAL" not in self._topology:
            self._topology["EQUAL"] = []
        self._topology["EQUAL"].append(map_)

    def get_equivalent(self):
        """!Return a list of map objects with equivalent temporal extent as this map

           @return A list of map objects or None
        """
        if "EQUAL" not in self._topology:
            return None
        return self._topology["EQUAL"]

    def append_starts(self, map_):
        """!Append a map that this map temporally starts with

           @param map_: This object should be of type AbstractMapDataset 
                        or derived classes
        """
        if "STARTS" not in self._topology:
            self._topology["STARTS"] = []
        self._topology["STARTS"].append(map_)

    def get_starts(self):
        """!Return a list of map objects that this map temporally starts with

           @return A list of map objects or None
        """
        if "STARTS" not in self._topology:
            return None
        return self._topology["STARTS"]

    def append_started(self, map_):
        """!Append a map that this map temporally started with

           @param map_: This object should be of type AbstractMapDataset 
                        or derived classes
        """
        if "STARTED" not in self._topology:
            self._topology["STARTED"] = []
        self._topology["STARTED"].append(map_)

    def get_started(self):
        """!Return a list of map objects that this map temporally started with

           @return A list of map objects or None
        """
        if "STARTED" not in self._topology:
            return None
        return self._topology["STARTED"]

    def append_finishes(self, map_):
        """!Append a map that this map temporally finishes with

           @param map_: This object should be of type AbstractMapDataset 
                        or derived classes
        """
        if "FINISHES" not in self._topology:
            self._topology["FINISHES"] = []
        self._topology["FINISHES"].append(map_)

    def get_finishes(self):
        """!Return a list of map objects that this map temporally finishes with

           @return A list of map objects or None
        """
        if "FINISHES" not in self._topology:
            return None
        return self._topology["FINISHES"]

    def append_finished(self, map_):
        """!Append a map that this map temporally finished with

           @param map_: This object should be of type AbstractMapDataset 
                        or derived classes
        """
        if "FINISHED" not in self._topology:
            self._topology["FINISHED"] = []
        self._topology["FINISHED"].append(map_)

    def get_finished(self):
        """!Return a list of map objects that this map temporally finished with

           @return A list of map objects or None
        """
        if "FINISHED" not in self._topology:
            return None
        return self._topology["FINISHED"]

    def append_overlaps(self, map_):
        """!Append a map that this map temporally overlaps

           @param map_: This object should be of type AbstractMapDataset 
                        or derived classes
        """
        if "OVERLAPS" not in self._topology:
            self._topology["OVERLAPS"] = []
        self._topology["OVERLAPS"].append(map_)

    def get_overlaps(self):
        """!Return a list of map objects that this map temporally overlaps

           @return A list of map objects or None
        """
        if "OVERLAPS" not in self._topology:
            return None
        return self._topology["OVERLAPS"]

    def append_overlapped(self, map_):
        """!Append a map that this map temporally overlapped

           @param map_: This object should be of type AbstractMapDataset 
                        or derived classes
        """
        if "OVERLAPPED" not in self._topology:
            self._topology["OVERLAPPED"] = []
        self._topology["OVERLAPPED"].append(map_)

    def get_overlapped(self):
        """!Return a list of map objects that this map temporally overlapped

           @return A list of map objects or None
        """
        if "OVERLAPPED" not in self._topology:
            return None
        return self._topology["OVERLAPPED"]

    def append_follows(self, map_):
        """!Append a map that this map temporally follows

           @param map_: This object should be of type AbstractMapDataset 
                        or derived classes
        """
        if "FOLLOWS" not in self._topology:
            self._topology["FOLLOWS"] = []
        self._topology["FOLLOWS"].append(map_)

    def get_follows(self):
        """!Return a list of map objects that this map temporally follows

           @return A list of map objects or None
        """
        if "FOLLOWS" not in self._topology:
            return None
        return self._topology["FOLLOWS"]

    def append_precedes(self, map_):
        """!Append a map that this map temporally precedes

           @param map_: This object should be of type AbstractMapDataset 
                        or derived classes
        """
        if "PRECEDES" not in self._topology:
            self._topology["PRECEDES"] = []
        self._topology["PRECEDES"].append(map_)

    def get_precedes(self):
        """!Return a list of map objects that this map temporally precedes

           @return A list of map objects or None
        """
        if "PRECEDES" not in self._topology:
            return None
        return self._topology["PRECEDES"]

    def append_during(self, map_):
        """!Append a map that this map is temporally located during
           This includes temporal relationships starts and finishes

           @param map_: This object should be of type 
                        AbstractMapDataset or derived classes
        """
        if "DURING" not in self._topology:
            self._topology["DURING"] = []
        self._topology["DURING"].append(map_)

    def get_during(self):
        """!Return a list of map objects that this map is temporally located during
           This includes temporally relationships starts and finishes

           @return A list of map objects or None
        """
        if "DURING" not in self._topology:
            return None
        return self._topology["DURING"]

    def append_contains(self, map_):
        """!Append a map that this map temporally contains
           This includes temporal relationships started and finished

           @param map_: This object should be of type AbstractMapDataset 
                        or derived classes
        """
        if "CONTAINS" not in self._topology:
            self._topology["CONTAINS"] = []
        self._topology["CONTAINS"].append(map_)

    def get_contains(self):
        """!Return a list of map objects that this map temporally contains
           This includes temporal relationships started and finished

           @return A list of map objects or None
        """
        if "CONTAINS" not in self._topology:
            return None
        return self._topology["CONTAINS"]

    def _generate_map_list_string(self, map_list, line_wrap=True):
        count = 0
        string = ""
        for map_ in map_list:
            if line_wrap and count > 0 and count % 3 == 0:
                string += "\n | ............................ "
                count = 0
            if count == 0:
                string += map_.get_id()
            else:
                string += ",%s" % map_.get_id()
            count += 1

        return string
    
    # Set the properties
    equivalent = property(fget=get_equivalent, 
                                       fset=append_equivalent)
    follows = property(fget=get_follows, 
                                    fset=append_follows)
    precedes = property(fget=get_precedes, 
                                     fset=append_precedes)
    overlaps = property(fget=get_overlaps, 
                                     fset=append_overlaps)
    overlapped = property(fget=get_overlapped, 
                                       fset=append_overlapped)
    during = property(fget=get_during, 
                                   fset=append_during)
    contains = property(fget=get_contains, 
                                     fset=append_contains)
    starts = property(fget=get_starts, 
                                     fset=append_starts)
    started = property(fget=get_started, 
                                     fset=append_started)
    finishes = property(fget=get_finishes, 
                                     fset=append_finishes)
    finished = property(fget=get_finished, 
                                     fset=append_finished)

    def print_topology_info(self):
        """!Print information about this class in human readable style"""
        _next = self.next()
        _prev = self.prev()
        _equal = self.get_equivalent()
        _follows = self.get_follows()
        _precedes = self.get_precedes()
        _overlaps = self.get_overlaps()
        _overlapped = self.get_overlapped()
        _during = self.get_during()
        _contains = self.get_contains()
        _starts = self.get_starts()
        _started = self.get_started()
        _finishes = self.get_finishes()
        _finished = self.get_finished()
        
        print " +-------------------- Temporal Topology -------------------------------------+"
        #          0123456789012345678901234567890
        if _next is not None:
            print " | Next: ...................... " + str(_next.get_id())
        if _prev is not None:
            print " | Previous: .................. " + str(_prev.get_id())
        if _equal is not None:
            print " | Equivalent: ................ " + \
                self._generate_map_list_string(_equal)
        if _follows is not None:
            print " | Follows: ................... " + \
                self._generate_map_list_string(_follows)
        if _precedes is not None:
            print " | Precedes: .................. " + \
                self._generate_map_list_string(_precedes)
        if _overlaps is not None:
            print " | Overlaps: .................. " + \
                self._generate_map_list_string(_overlaps)
        if _overlapped is not None:
            print " | Overlapped: ................ " + \
                self._generate_map_list_string(_overlapped)
        if _during is not None:
            print " | During: .................... " + \
                self._generate_map_list_string(_during)
        if _contains is not None:
            print " | Contains: .................. " + \
                self._generate_map_list_string(_contains)
        if _starts is not None:
            print " | Starts:.. .................. " + \
                self._generate_map_list_string(_starts)
        if _started is not None:
            print " | Started:. .................. " + \
                self._generate_map_list_string(_started)
        if _finishes is not None:
            print " | Finishes:................... " + \
                self._generate_map_list_string(_finishes)
        if _finished is not None:
            print " | Finished:................... " + \
                self._generate_map_list_string(_finished)

    def print_topology_shell_info(self):
        """!Print information about this class in shell style"""

        _next = self.next()
        _prev = self.prev()
        _equal = self.get_equivalent()
        _follows = self.get_follows()
        _precedes = self.get_precedes()
        _overlaps = self.get_overlaps()
        _overlapped = self.get_overlapped()
        _during = self.get_during()
        _contains = self.get_contains()
        _starts = self.get_starts()
        _started = self.get_started()
        _finishes = self.get_finishes()
        _finished = self.get_finished()
        
        if _next is not None:
            print "next=" + _next.get_id()
        if _prev is not None:
            print "prev=" + _prev.get_id()
        if _equal is not None:
            print "equivalent=" + self._generate_map_list_string(_equal, False)
        if _follows is not None:
            print "follows=" + self._generate_map_list_string(_follows, False)
        if _precedes is not None:
            print "precedes=" + self._generate_map_list_string(
                _precedes, False)
        if _overlaps is not None:
            print "overlaps=" + self._generate_map_list_string(
                _overlaps, False)
        if _overlapped is not None:
            print "overlapped=" + \
                self._generate_map_list_string(_overlapped, False)
        if _during is not None:
            print "during=" + self._generate_map_list_string(_during, False)
        if _contains is not None:
            print "contains=" + self._generate_map_list_string(
                _contains, False)
        if _starts is not None:
            print "starts=" + \
                self._generate_map_list_string(_starts)
        if _started is not None:
            print "started=" + \
                self._generate_map_list_string(_started)
        if _finishes is not None:
            print "finishes=" + \
                self._generate_map_list_string(_finishes)
        if _finished is not None:
            print "finished=" + \
                self._generate_map_list_string(_finished)

###############################################################################

if __name__ == "__main__":
    import doctest
    doctest.testmod()