import logging
import numpy as np
import os
import time
from datetime import datetime

import base.lock
import base.proto
from mapping.distributed_mapping.proto.elapsed_time_pb2 import ElapsedTime

def callout_print(*args, **kwargs):
    """ Print process stage."""
    logging.info('')
    logging.info('-'*80)
    logging.info(*args, **kwargs)
    logging.info('-'*80)
    logging.info('')


class Timer(object):
    """ Object used for timing execution of algorithms. Can save timing results to a file. Locks timing file while reading/writing, so that multiple processes can write their timing information to the same file simultaneously. """

    def __init__(self):
        """ Initialize the object and start the timer. """
        self.start_ = time.time()

    def reset(self):
        """ Resets the timer. """
        self.start_ = time.time()

    def save(self, id, timing_filename):
        """ Save time elapsed to a file as an ElapsedTime proto. If a file already contains time measurements from other processes, current time is appended to it.

        Arguments:
        id - A string identifier (e.g. process name).
        timing_filename - A path to the output filename.
        """

        self.end_ = time.time()
        self.interval_ = self.end_ - self.start_
        logging.info('| Elapsed: {:3f} s'.format(self.interval_))

        redis_lock_name = str(hash(os.path.abspath(timing_filename)))
        timing_proto = ElapsedTime()
        with base.lock.lock(redis_lock_name, blocking=True):
            if not base.proto.ReadProto(timing_filename, timing_proto):
                logging.error("Could not read timing proto file '{}'.".format(timing_filename))
                return False
            timing_proto.elapsed_time[id] = self.interval_
            if not base.proto.WriteProto(timing_filename, timing_proto):
                logging.error("Could not write to timing proto file '{}'.".format(timing_filename))
                return False

            return True


def timing_statistics(timing_filename):
    """ Read a filename with ElapsedTime proto and calculate timing statistics. Returns a dictionary with 'min', 'max, 'mean' and 'median' times.

    Arguments:
    timing_filename - A path to the input filename containing an ElapsedTime proto.
    """

    # Read timings.
    timing_proto = ElapsedTime()
    assert base.proto.ReadProtoAsText(timing_filename, timing_proto), "Could not read timing proto file '{}'.".format(timing_filename)

    elapsed_times = []
    run_ids = []
    for run_id in timing_proto.elapsed_time:
        elapsed_times.append(timing_proto.elapsed_time[run_id])
        run_ids.append(os.path.basename(run_id))

    # Calculate statistics.
    timing_stats = {}
    timing_stats['min']       = np.min(elapsed_times)
    timing_stats['max']       = np.max(elapsed_times)
    timing_stats['mean']      = np.mean(elapsed_times)
    timing_stats['median']    = np.median(elapsed_times)

    return timing_stats


def utc_timestamp_for_folder_name(timestamp_fmt="%Y%m%d_%H%M%S"):
    return datetime.utcnow().strftime(timestamp_fmt)
