#    TOMUSS: The Online Multi User Simple Spreadsheet
#    Copyright (C) 2008-2014 Thierry EXCOFFIER, Universite Claude Bernard
#
#    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 2 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, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
#    Contact: Thierry.EXCOFFIER@univ-lyon1.fr

import os
import time
import cgi
from . import utilities
from . import configuration

warn = utilities.warn

def client_ip(server):
    try:
        # In case of proxy
        ip = server.headers["x-forwarded-for"]
        try:
            # Take the first IP
            return utilities.safe(ip.split(",")[0])
        except IndexError:
            return utilities.safe(ip)
    except KeyError:
        return utilities.safe(server.client_address[0])

class Ticket(object):
    def __init__(self, ticket, user_name=None, user_ip=None,
                 user_browser=None, date=None, language=''):
        self.ticket = ticket
        self.user_name = user_name
        self.user_ip = user_ip
        self.user_browser = user_browser
        if date == None:
            self.date = time.time()
        else:
            self.date = date
        self.set_language(language)

    def set_language(self, lang):
        self.language = lang.lower().replace(';',',').replace('-','_')

    def is_member_of(self, group):
        return configuration.is_member_of(self.user_name, group)

    # Compatibility with old code
    is_a_teacher         = property(lambda x:x.is_member_of('staff'))
    is_an_abj_master     = property(lambda x:x.is_member_of('abj_masters'))
    is_a_referent_master = property(lambda x:x.is_member_of('referent_masters'))
    is_a_referent        = property(lambda x:x.is_member_of('referents'))
    is_an_administrative = property(lambda x:x.is_member_of('administratives'))
        
    def is_fine(self, server):
        # print self.user_name, (time.time() - self.date) , configuration.ticket_time_to_live, self.user_ip, client_ip(server), self.user_browser, server.headers["user-agent"]
        if (configuration.regtest
            and self.user_name == self.ticket
            and self.user_ip == client_ip(server)
            ):
            return True
        user_browser = server.headers.get("user-agent", '')
        if user_browser == '':
            utilities.send_mail_in_background(
                configuration.maintainer,
                'BUG TOMUSS: No user Agent !',
                'user_ip = %s, client_ip = %s, user_name = %s' % (
                self.user_ip,
                client_ip(server),
                self.user_name))

        return (self.user_name != None
                and (time.time() - self.date) < configuration.ticket_time_to_live
                and self.user_ip == client_ip(server)
                and self.user_browser == user_browser
                )

    def log(self):
        return 'add(' + \
               repr(self.ticket) + ',' + repr(self.user_name) + \
               ',' + repr(self.user_ip) + ',' + repr(self.user_browser) + \
               ',' + repr(self.date) + ')\n'

    def remove_file(self):
        try:
            os.unlink(os.path.join(configuration.ticket_directory, self.ticket))
        except IOError:
            pass
        except OSError:
            pass

    def remove(self):
        """Remove all the tickets of the user to avoid problems"""
        warn('Remove ticket : %s' % self, what="auth")
        user = self.user_name
        for key, value in list(tickets.items()):
            if value.user_name == user:
                warn('Delete ticket : %s' % tickets[key], what="auth")
                tickets[key].date = 0
                tickets[key].remove_file()

    def access_right(self):
        if not hasattr(self,'is_a_teacher'):
            return '?????'
        s = ''
        if self.is_a_teacher:
            s += 'T'
        if self.is_an_abj_master:
            s += 'J'
        if self.is_a_referent_master:
            s += 'R'
        if self.is_a_referent:
            s += 'r'
        if self.is_an_administrative:
            s += 'A'
        if getattr(self, 'password_ok', False):
            s += 'P'
        return s

    def __str__(self):
        return self.log()

    def backtrace_html(self):
        return cgi.escape(self.log())


# A class is better....

tickets = {}

def add(ticket, user_name, user_ip, user_browser, date=None, language=''):
    t = Ticket(ticket, user_name, user_ip, user_browser, date, language)
    tickets[ticket] = t
    return t

def add_ticket(ticket, user_name, user_ip, user_browser, language=''):
    get_ticket_objet.the_lock.acquire()
    t = add(ticket, user_name, user_ip, user_browser, language=language)
    get_ticket_objet.the_lock.release()
    utilities.write_file(os.path.join(configuration.ticket_directory, t.ticket), t.log())
    return t

def get_ticket_string(server):
    """Extract from the path the ticket as a string (or None) and the path"""
    warn('PATH: %s' % server.path, what='auth')
    if server.path.startswith("/="):
        path = server.path.split("/")
        ticket = path[1][1:]
        path = path[2:]
    else:
        ticket = configuration.authenticator.ticket_from_url(server)
        path = server.path.split("?")[0].lstrip('/').split('/')

    return ticket, [cgi.urllib.unquote(x)
                    for x in path]

def clone(ticket_key, ticket):
    return Ticket(ticket_key,
                  ticket.user_name, ticket.user_ip, ticket.user_browser,
                  ticket.date, ticket.language)

def remove_old_tickets():
    """Remove old tickets from memory"""
    if time.time() - remove_old_tickets.time < configuration.ticket_time_to_live/10:
        return
    remove_old_tickets.time = time.time()

    assert(get_ticket_objet.the_lock.locked)
    global tickets
    t = {}
    for ticket in tickets.values():
        if (time.time() - ticket.date) > configuration.ticket_time_to_live \
               or ticket.user_name == '':
            ticket.remove_file()
        else:
            t[ticket.ticket] = ticket

    tickets = t
    
remove_old_tickets.time = time.time()

def remove_old_files():
    """Remove old tickets from disc.
    It should be done because TOMUSS may crash without cleaning
    """
    for filename in os.listdir(configuration.ticket_directory):
        filename = os.path.join(configuration.ticket_directory, filename)
        try:
            if time.time() - os.path.getmtime(filename) \
                   > configuration.ticket_time_to_live:
                os.unlink(filename)
        except OSError:
            pass

@utilities.add_a_lock
def get_ticket_objet(ticket, server):
    """Get the ticket object from the ticket string
       * None if no ticket
       * False if ticket invalid to quickly
       * 0 if ticket not fine
    """
    
    if ticket == None:
        warn('No ticket', what='auth')
        return None
    if ticket not in tickets:
        warn('Unknown ticket: ' + ticket, what='auth')
        ticket_file = os.path.join(configuration.ticket_directory, ticket)
        try:
            # Update 'tickets' table with 'add' function
            eval( utilities.read_file(ticket_file))
            warn('Found in file', what='auth')
        except OSError:
            pass
        except IOError:
            pass

    remove_old_tickets()

    ticket_object = tickets.get(ticket, None)

    if ticket_object  and not ticket_object.is_fine(server):
        warn('TICKET NOT FINE : %.3f secs, %s %s %s' % (
                time.time() - ticket_object.date,
                str(ticket_object).strip(),
                client_ip(server), server.headers.get("user-agent", ''))
             )
        for k, v in server.headers.items():
            warn('%s : %s' % (k, v))
        
        if time.time() - ticket_object.date < 2:
            return False
        return 0
    if ticket_object and ticket_object.language == '':
        ticket_object.set_language(server.headers.get('accept-language',''))
    return ticket_object 
