# Copyright (c) 2004-2005 DoCoMo Euro-Labs GmbH (Munich, Germany).
# Copyright (c) 2001-2005 LOGILAB S.A. (Paris, FRANCE).
#
# http://www.docomolab-euro.com/ -- mailto:tarlano@docomolab-euro.com
# http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
"""narval IM presence manipulation's related actions


:version: $Revision:$  
:author: Logilab

:copyright:
  2000-2005 LOGILAB S.A. (Paris, FRANCE)
  
  2004-2005 DoCoMo Euro-Labs GmbH (Munich, Germany)
  
:contact:
  http://www.logilab.fr/ -- mailto:contact@logilab.fr
  
  http://www.docomolab-euro.com/ -- mailto:tarlano@docomolab-euro.com
"""

__revision__ = '$Id:$'
__docformat__ = 'restructuredtext en'

import sys
from time import mktime, localtime

# days of week index in a local time tuple
DOWIDX = 6 

class Away:
    """class representing a time slot where some jabber user has been
    away
    """
    def __init__(self, start, end_time=None):
        self.status = start['status']
        self.show = start['show']
        self.start_time = start['time']
        self.duration = 0
        self.end_time = None
        if end_time is not None:
            self.back(end_time)

    def __repr__(self):
        return "Away({'status': %(status)r, 'show': %(show)r, 'time': \
%(start_time)r}, end_time=%(end_time)r)" % self.__dict__

    def __eq__(self, other):
        """for test purpose"""
        return (other.status == self.status
                and other.show == self.show
                and other.start_time == self.start_time
                and other.end_time == self.end_time
                and other.duration == self.duration)
    
    def __ne__(self, other):
        return not self == other
                
    def back(self, end_time):
        """complete data with user's back time"""
        assert end_time > self.start_time
        self.end_time = end_time
        self.duration = mktime(self.end_time) - mktime(self.start_time)


def is_available(pres_info):
    """return True if the presence information means the user is
    available
    """
    return (not pres_info['show']
            and pres_info['status'].lower() != 'disconnected')

def away_from_presences(presences):
    """return a generator on Away instances from an ordered list of raw
    presence information
    """
    current = None
    for pres_info in presences:
        if current is None:
            # waiting for an unavailable presence
            if not is_available(pres_info):
                current = Away(pres_info)
            else:
                print >> sys.stderr, 'skipping presence', pres_info, \
                      '(waiting for a show="something" element)'
        else:
            # waiting for an available presence
            if is_available(pres_info):
                current.back(pres_info['time'])
                yield current
                current = None
            else:
                current.back(pres_info['time'])
                yield current
                current = Away(pres_info)
            

def similarity_factor(away_ref, away):
    """return a similarity factor between a reference presence
    information dict and another (usually extracted from a pre-recorded
    data set)

    the similarity factor is a float value between 0 and 2
    """
    if away_ref.show == away.show \
           and away_ref.status == away.status:
        return 2.
    datetime_ref, datetime = away_ref.start_time, away.start_time
    factor = 0.
    if away_ref.show == away.show:
        factor += 0.2
    if away_ref.status == away.status:
        factor += 0.4
    if datetime_ref[DOWIDX] == datetime[DOWIDX]:
        factor += 0.4
    minutes_ref, minutes = mktime(datetime_ref) / 60, mktime(datetime) / 60
    if (minutes - 30) <= minutes_ref <= (minutes + 30):
        factor += 0.6
    return factor


class PresenceAnalysisError(Exception): pass

def expected_time_back(presences, minimum=None):
    """return a local time tuple indicating the expected time where
    the user whose presence data come from will be back
    """
    pres_info_ref = presences[-1]
    if is_available(pres_info_ref):
        raise PresenceAnalysisError('%s is present')
    away_ref = Away(pres_info_ref)
    expected_duration = 0
    diviser = 0
    for away in away_from_presences(presences):
        if not minimum or (minimum and away.duration > minimum):
            factor = similarity_factor(away_ref, away)
            expected_duration += away.duration * factor
            diviser += factor
    try:
        expected_duration = expected_duration / diviser
    except ZeroDivisionError:
        raise PresenceAnalysisError('no data for %s')
    # FIXME: variance ?
    return localtime(mktime(away_ref.start_time) + expected_duration)

def extrapol_time_back(presences, elapsed_period):
    """return a local time tuple indicating the extrapolated time where
    the user whose presence data come from will be back
    """
    return expected_time_back(presences, elapsed_period)





import unittest
from logilab.common import testlib

TEST_DATA = [{'status': 'un peu de sport', 'time': (2005, 1, 24, 18, 3, 21, 0, 24, 0), 'show': 'away'},
 {'status': '', 'show': '', 'time': (2005, 1, 25, 9, 30, 18, 1, 25, 0)},
 {'status': 'Sorry, I ran out for a bit!', 'show': 'away', 'time': (2005, 1, 25, 9, 31, 41, 1, 25, 0)},
 {'status': '', 'show': '', 'time': (2005, 1, 25, 9, 33, 32, 1, 25, 0)},
 {'status': 'Sorry, I ran out for a bit!', 'show': 'away', 'time': (2005, 1, 25, 9, 37, 21, 1, 25, 0)},
 {'status': '', 'show': '', 'time': (2005, 1, 25, 9, 41, 1, 1, 25, 0)},
 {'status': 'Sorry, I ran out for a bit!', 'show': 'away', 'time': (2005, 1, 25, 9, 43, 41, 1, 25, 0)},
 {'status': '', 'show': '', 'time': (2005, 1, 25, 10, 12, 20, 1, 25, 0)},
 {'status': 'Sorry, I ran out for a bit!', 'show': 'away', 'time': (2005, 1, 25, 10, 18, 21, 1, 25, 0)},
 {'status': '', 'show': '', 'time': (2005, 1, 25, 11, 14, 20, 1, 25, 0)},
 {'status': "c'est la pause", 'show': 'away', 'time': (2005, 1, 25, 11, 33, 1, 1, 25, 0)},
 {'status': '', 'show': '', 'time': (2005, 1, 25, 11, 36, 27, 1, 25, 0)},
 {'status': "c'est la pause", 'show': 'away', 'time': (2005, 1, 25, 12, 5, 1, 1, 25, 0)},
 {'status': '', 'show': '', 'time': (2005, 1, 25, 12, 12, 9, 1, 25, 0)},
 {'status': "c'est la pause", 'show': 'away', 'time': (2005, 1, 25, 12, 23, 21, 1, 25, 0)},
 {'status': '', 'show': '', 'time': (2005, 1, 25, 13, 51, 7, 1, 25, 0)},
 {'status': "c'est la pause", 'show': 'away', 'time': (2005, 1, 25, 15, 56, 21, 1, 25, 0)}
             ]

class ExpectedTimeBackTC(unittest.TestCase):
    def test(self):
        self.assertEquals(expected_time_back(TEST_DATA),
                          (2005, 1, 25, 16, 14, 38, 1, 25, 0))
        
class AwayFromPresencesTC(testlib.TestCase):
    def test(self):
        self.assertListEquals(list(away_from_presences(TEST_DATA)),
                              [Away({'status': 'un peu de sport', 'show': 'away', 'time': (2005, 1, 24, 18, 3, 21, 0, 24, 0)}, end_time=(2005, 1, 25, 9, 30, 18, 1, 25, 0)),
                               Away({'status': 'Sorry, I ran out for a bit!', 'show': 'away', 'time': (2005, 1, 25, 9, 31, 41, 1, 25, 0)}, end_time=(2005, 1, 25, 9, 33, 32, 1, 25, 0)),
                               Away({'status': 'Sorry, I ran out for a bit!', 'show': 'away', 'time': (2005, 1, 25, 9, 37, 21, 1, 25, 0)}, end_time=(2005, 1, 25, 9, 41, 1, 1, 25, 0)),
                               Away({'status': 'Sorry, I ran out for a bit!', 'show': 'away', 'time': (2005, 1, 25, 9, 43, 41, 1, 25, 0)}, end_time=(2005, 1, 25, 10, 12, 20, 1, 25, 0)),
                               Away({'status': 'Sorry, I ran out for a bit!', 'show': 'away', 'time': (2005, 1, 25, 10, 18, 21, 1, 25, 0)}, end_time=(2005, 1, 25, 11, 14, 20, 1, 25, 0)),
                               Away({'status': "c'est la pause", 'show': 'away', 'time': (2005, 1, 25, 11, 33, 1, 1, 25, 0)}, end_time=(2005, 1, 25, 11, 36, 27, 1, 25, 0)),
                               Away({'status': "c'est la pause", 'show': 'away', 'time': (2005, 1, 25, 12, 5, 1, 1, 25, 0)}, end_time=(2005, 1, 25, 12, 12, 9, 1, 25, 0)),
                               Away({'status': "c'est la pause", 'show': 'away', 'time': (2005, 1, 25, 12, 23, 21, 1, 25, 0)}, end_time=(2005, 1, 25, 13, 51, 7, 1, 25, 0))])
        
class SimilarityFactorTC(unittest.TestCase):

    def test_identical(self):
        d = Away({'status': '', 'show': '', 'time': (2005, 1, 25, 15, 55, 17, 1, 25, 0)})
        self.assertEquals(similarity_factor(d, d), 1)
        
    def test_diff_status(self):
        d1 = Away({'status': '', 'show': '', 'time': (2005, 1, 25, 15, 55, 17, 1, 25, 0)})
        d2 = Away({'status': 'autre chose', 'show': '', 'time': (2005, 1, 25, 15, 55, 17, 1, 25, 0)})
        self.assertAlmostEquals(similarity_factor(d1, d2), 0.7)
        
    def test_diff_show(self):
        d1 = Away({'status': '', 'show': '', 'time': (2005, 1, 25, 15, 55, 17, 1, 25, 0)})
        d2 = Away({'status': '', 'show': 'bla', 'time': (2005, 1, 25, 15, 55, 17, 1, 25, 0)})
        self.assertEquals(similarity_factor(d1, d2), 0.8)
        
    def test_time(self):
        d1 = Away({'status': '', 'show': 'bla', 'time': (2005, 1, 25, 15, 55, 17, 1, 25, 0)})
        d2 = Away({'status': 'bla', 'show': '', 'time': (2005, 1, 25, 15, 25, 17, 1, 25, 0)})
        self.assertEquals(similarity_factor(d1, d2), 0.5)
        d1 = Away({'status': '', 'show': 'bla', 'time': (2005, 1, 25, 15, 55, 17, 1, 25, 0)})
        d2 = Away({'status': 'bla', 'show': '', 'time': (2005, 1, 25, 16, 5, 17, 1, 25, 0)})
        self.assertEquals(similarity_factor(d1, d2), 0.5)
        d1 = Away({'status': '', 'show': 'bla', 'time': (2005, 1, 25, 15, 55, 17, 1, 25, 0)})
        d2 = Away({'status': 'bla', 'show': '', 'time': (2005, 1, 25, 15, 5, 17, 1, 25, 0)})
        self.assertEquals(similarity_factor(d1, d2), 0.2)
        
    def test_day_of_week(self):
        d1 = Away({'status': '', 'show': 'bla', 'time': (2005, 1, 25, 15, 55, 17, 1, 25, 0)})
        d2 = Away({'status': 'bla', 'show': '', 'time': (2005, 1, 25, 15, 26, 17, 1, 25, 0)})
        self.assertEquals(similarity_factor(d1, d2), 0.5)
        d1 = Away({'status': '', 'show': 'bla', 'time': (2005, 1, 25, 15, 55, 17, 1, 25, 0)})
        d2 = Away({'status': 'bla', 'show': '', 'time': (2005, 1, 25, 15, 26, 17, 2, 25, 0)})
        self.assertEquals(similarity_factor(d1, d2), 0.3)                        


if __name__ == '__main__':
    unittest.main()
