#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
#  Copyright 2011, De Verkeersonderneming <rits@verkeersonderneming.nl>
#
#  This file is part of PyRITS - A tool for processing and analyzing transport
#  management system data.
#
#  PyRITS 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.
#
#  PyRITS 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/>.

"""Calculate driving times and driving delays for database Erniesoft.

The following type codes are used in the driving times algorithm. The
description of each type code is described in the table below::

     typecode |                    description
    ----------+---------------------------------------------------
            0 | Basic Sign Of Life
            1 | User Login
            2 | User Logout
            3 | Driver Switch
           10 | Activity Started
           11 | Activity Cancelled
           12 | Activity Report
           13 | Activity End
           14 | Activity Join
           15 | Activity Leave
           16 | Activity Switch
           17 | PTO alarm
           18 | PTO start
           19 | PTO end
           20 | Task Received
           21 | Task Accepted
           22 | Task Refused
           23 | Task Busy
           24 | Task Cancelled
           25 | Task Finished
           30 | Outgoing Phone Started
           31 | Incoming Phone Started
           32 | Outgoing Phone Finished
           33 | Incoming Phone Finished
           40 | Navigation Started
           41 | Navigation Cancelled
           42 | Navigation ETA update
           43 | Navigation Destination Reached
           50 | Start of Speed Limit Violation
           51 | Start of Peak RPM violation
           52 | Start of Average RPM violation
           53 | Acceleration Limit Violation
           54 | Acceleration Limit Violation End
           55 | Engine Idle Violation
           56 | Enter Geofence
           57 | Exit Geofence
           58 | Panic Alert
           59 | Maximum Activity Duration Exceeded
           60 | End of Speed Limit Violation
           61 | End of Peak RPM Lmit Violation
           62 | End of Average RPM Limit Violation
           65 | Update of Speed Limit 
Violation
           66 | Update of Peak RPM Limit Violation
         
  67 | Update of Average RPM Limit Violation
           71 | Contact ON
           72 | Contact OFF
           73 | System Shutdown
           75 | Violation of Engine Lock
           76 | Motion Alert (Vehicle Moves while contact is off)
           77 | Vehicle Immobilization
           78 | Power Disconnect (Run on Battery)
           79 | Battery Low
           82 | Driving Times State Event
           83 | Driving Times Driving Warning
           84 | Driving Times Driving Violation
           85 | Driving Times Total Driving Warning
           90 | Temperature Sensor Violation
           91 | Temperature Sensor Violation Reminder
           92 | Coolant Temperature Violation
           93 | Coolant Temperature Violation Reminder
           97 | Wrong Trailer Tethered
           98 | Trailer Tethered
           99 | Trailer Untethered
          200 | GPRS Status Info
"""

import sys
import os
import math
import datetime
import logging
import csv

import psycopg2.extras

import pyrits.config
import pyrits.std
import pyrits.HTML
import pyrits.erniesoft.query

__author__ = "Serrano Pereira"
__copyright__ = "Copyright 2011, De Verkeersonderneming"
__credits__ = ["Serrano Pereira <serrano.pereira@gmail.com>"]
__license__ = "GPL3"
__version__ = "0.1.2"
__maintainer__ = "Serrano Pereira"
__email__ = "serrano.pereira@gmail.com"
__status__ = "Production"
__date__ = "2011/11/24"


DEBUG = 0

class Standard(object):
    """Super class for :class:`DrivingDelays` and :class:`DrivingTimes`."""

    def get_vehicles_from_date_range(self, date_start=None, date_end=None):
        """Return all vehicle codes that occur in a date range."""
        cursor = self.connection.cursor()
        if not date_start:
            cursor.execute("""SELECT DISTINCT terminalcode
            FROM hist_bcacties
            WHERE terminalcode IS NOT NULL;""")
        else:
            cursor.execute("""SELECT DISTINCT terminalcode
            FROM hist_bcacties
            WHERE tijd BETWEEN '%s' AND '%s'
            AND terminalcode IS NOT NULL;""" % (date_start, date_end))
        vehicle_codes = []
        for t in cursor:
            vehicle_codes.append(t[0])
        cursor.close()
        return vehicle_codes

class DrivingDelays(Standard):
    """Driving delays calculator.

    It calculates the clean driving times, the planned driving delays, and the
    realized driving delays.
    """

    def __init__(self, connection):
        self.connection = connection
        self.location_combinations = {}
        self.reset()

    def reset(self):
        """Set variables with default values."""
        self.task_routes = {}
        self.task_delays = {}
        self.clean_driving_times = {}
        self.route_distances = {}
        self.static_tasks = []

    def __set_static_tasks(self):
        """Set a variable with all static tasks id's.

        Static tasks are tasks for which the vehicle has not moved. The task
        id's for static tasks are saved to variable `self.static_tasks`. The
        static tasks are required for method :meth:`__set_clean_driving_times`.
        """
        cursor = self.connection.cursor()
        cursor.execute("SELECT task_id FROM driving_times WHERE static = 't';")
        for task_id in cursor:
            self.static_tasks.append(task_id[0])
        cursor.close()

    def __set_clean_driving_times(self, vehicle_code):
        """Calculate the clean driving times.

        In order to calculate the clean driving times, first the driving
        distance for each route is retrieved from the database. Than this
        distances and the average driving time for a given distance is used to
        calculate the clean driving time.

        If the clean driving time is already present in the database, it will
        not calculate the clean driving time.

        It will set the clean driving time for static tasks to 0.

        .. note::

            Usage of method :meth:`__set_clean_driving_times_from_modelit` is
            favored over this method. This method was removed from the default
            routines.
        """
        # First set the static tasks.
        self.__set_static_tasks()

        # This query lists the visited locations in the realized order. This
        # is used to determine the route for each task.
        cursor = self.connection.cursor()
        cursor.execute("""SELECT r.ritregelnr, r.locatiecode
        FROM tbl_ritregels AS r
        INNER JOIN hist_bcacties AS a ON a.elipsref = r.ritregelnr
        INNER JOIN task_to_vehicle as v ON v.task_id = r.ritregelnr
        WHERE v.vehicle_code='%s'
        GROUP BY r.ritregelnr,r.locatiecode
        ORDER BY MIN(a.tijd);""" % (vehicle_code))

        # Define location A and location B for each task.
        location_a = None
        for task_id,location_b in cursor:
            # Convert location names consisting of spaces to an empty string.
            if location_b:
                location_b = location_b.strip()

            # Add a location tuple to the tuples list if both locations are set.
            if bool(location_a) and bool(location_b):
                tuple = [location_a,location_b]
                # By sorting the tuple, retrieval from the database will be
                # faster if to be retrieved tuples are sorted as well.
                tuple.sort()
                # Save the tuple.
                self.task_routes[task_