from __future__ import print_function
from builtins import map
from builtins import object
import abc
import argparse
import logging
import time

from datetime import datetime
from fn import _
from future.utils import with_metaclass

log = logging.getLogger(__name__)
display_log = logging.getLogger(__name__ + ".DisplayPlugin")

argparser = argparse.ArgumentParser(add_help=False)
argparser.add_argument('--results-log',
    help="file to store log of the best configuration times")
argparser.add_argument('--results-log-details',
    help="file to store log of the non-best configuration times")
argparser.add_argument('--quiet', action='store_true',
    help="print less information")
argparser.add_argument('--display-frequency', default=10, type=int,
    help="how often for DisplayPlugin to print")

class SearchPlugin(object):
  @property
  def priority(self):
    """control order the plugin hooks gets run in, lower runs first"""
    return 0

  def set_driver(self, driver):
    """called before all other methods"""
    self.driver = driver

  def before_main(self): pass
  def after_main(self):  pass

  def before_techniques(self): pass
  def after_techniques(self):  pass

  def before_results_wait(self): pass
  def after_results_wait(self):  pass

  def on_result(self, result):
    """
    called once for every new result
    """
    pass

  def on_result_for_technique(self, result, technique):
    """
    called right before a result is given to a technique
    (result may be requested by multiple techniques)
    """
    pass

  def on_new_best_result(self, result):
    """
    called whenever the global best result changes
    """
    pass

class DisplayPlugin(with_metaclass(abc.ABCMeta, SearchPlugin)):
  def __init__(self, display_period=5):
    super(DisplayPlugin, self).__init__()
    self.last  = time.time()
    self.start = time.time()
    self.display_period = display_period

  def after_results_wait(self):
    t = time.time()
    if t - self.display_period > self.last:
      # call display every 5 seconds
      self.last = t
      self.display(t)

  def after_main(self):
    self.display()

  @abc.abstractmethod
  def display(self, t=None):
    pass


class LogDisplayPlugin(DisplayPlugin):
  def display(self, t=None):
    if not t:
      t = time.time()
    count = self.driver.results_query().count()
    best = self.driver.results_query(objective_ordered = True).first()
    if best is None:
      log.warning("no results yet")
      return
    requestor = ','.join(map(_.requestor, best.desired_results))
    display_log.info("tests=%d, best %s, cost %s, found by %s",
                     count,
                     cfg_repr(best.configuration),
                     self.driver.objective.display(best),
                     requestor,
                     )

class FileDisplayPlugin(SearchPlugin):
  def __init__(self, out, details, *args, **kwargs):
    super(FileDisplayPlugin, self).__init__(*args, **kwargs)
    self.last_best = float('inf')
    self.start_date = datetime.now()
    if out:
      self.out = open(out, "w")
    else:
      self.out = None
    if out == details:
      self.details = self.out
      self.out = None
    elif details:
      self.details = open(details, "w")
    else:
      self.details = None

  def on_result(self, result):
    if self.out and result.time < self.last_best:
      self.last_best = result.time
      print((result.collection_date - self.start_date).total_seconds(), \
          result.time, file=self.out)
      self.out.flush()
    if self.details:
      print((result.collection_date - self.start_date).total_seconds(), \
          result.time, file=self.details)
      self.details.flush()

def get_enabled(args):
  plugins = []
  if not args.quiet:
    plugins.append(LogDisplayPlugin(args.display_frequency))
  if args.results_log or args.results_log_details:
    plugins.append(FileDisplayPlugin(args.results_log,
                                     args.results_log_details))
  return plugins

def cfg_repr(cfg):
  try:
    s = repr(cfg.data)
    if len(s) < 100:
      return s
  except:
    pass
  return "#{0}".format(cfg.id)






