# Copyright 2011-2013 James McCauley
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at:
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
Some of POX's core API and functionality is here, largely in the POXCore
class (an instance of which is available as pox.core.core).

This includes things like component rendezvous, logging, system status
(up and down events), etc.
"""

from __future__ import print_function

# Set up initial log state
import logging

import inspect
import time
import os

_path = inspect.stack()[0][1]
_ext_path = _path[0:_path.rindex(os.sep)]
_ext_path = os.path.dirname(_ext_path) + os.sep
_path = os.path.dirname(_path) + os.sep

SQUELCH_TIME = 5

_squelch = ''
_squelchTime = 0
_squelchCount = 0

def getLogger (name=None, moreFrames=0):
  """
  In general, you don't need to call this directly, and will use
  core.getLogger() instead.
  """
  if name is None:
    s = inspect.stack()[1+moreFrames]
    name = s[1]
    if name.endswith('.py'):
      name = name[0:-3]
    elif name.endswith('.pyc'):
      name = name[0:-4]
    if name.startswith(_path):
      name = name[len(_path):]
    elif name.startswith(_ext_path):
      name = name[len(_ext_path):]
    name = name.replace('/', '.').replace('\\', '.') #FIXME: use os.path or whatever

    # Remove double names ("topology.topology" -> "topology")
    if name.find('.') != -1:
      n = name.split('.')
      if len(n) >= 2:
        if n[-1] == n[-2]:
          del n[-1]
          name = '.'.join(n)

    if name.startswith("ext."):
      name = name.split("ext.",1)[1]

    if name.endswith(".__init__"):
      name = name.rsplit(".__init__",1)[0]

  l = logging.getLogger(name)
  g=globals()
  if not hasattr(l, "print"):
    def printmsg (*args, **kw):
      #squelch = kw.get('squelch', True)
      msg = ' '.join((str(s) for s in args))
      s = inspect.stack()[1]
      o = '['
      if 'self' in s[0].f_locals:
        o += s[0].f_locals['self'].__class__.__name__ + '.'
      o += s[3] + ':' + str(s[2]) + '] '
      o += msg
      if o == _squelch:
        if time.time() >= _squelchTime:
          l.debug("[Previous message repeated %i more times]" % (g['_squelchCount']+1,))
          g['_squelchCount'] = 0
          g['_squelchTime'] = time.time() + SQUELCH_TIME
        else:
          g['_squelchCount'] += 1
      else:
        g['_squelch'] = o
        if g['_squelchCount'] > 0:
          l.debug("[Previous message repeated %i more times]" % (g['_squelchCount'],))
        g['_squelchCount'] = 0
        g['_squelchTime'] = time.time() + SQUELCH_TIME
        l.debug(o)

    setattr(l, "print", printmsg)
    setattr(l, "msg", printmsg)

  return l


# Working around something (don't remember what)
log = (lambda : getLogger())()

from pox.lib.revent import *

# Now use revent's exception hook to put exceptions in event handlers into
# the log...
def _revent_exception_hook (source, event, args, kw, exc_info):
  try:
    c = source
    t = event
    if hasattr(c, "__class__"): c = c.__class__.__name__
    if isinstance(t, Event): t = t.__class__.__name__
    elif issubclass(t, Event): t = t.__name__
  except:
    pass
  log.exception("Exception while handling %s!%s...\n" % (c,t))
import pox.lib.revent.revent
pox.lib.revent.revent.handleEventException = _revent_exception_hook

class GoingUpEvent (Event):
  """ Fired when system is going up. """
  pass

class GoingDownEvent (Event):
  """ Fired when system is going down. """
  pass

class UpEvent (Event):
  """ Fired when system is up. """
  pass

class DownEvent (Event):
  """ Fired when system is down. """
  pass

class ComponentRegistered (Event):
  """
  This is raised by core whenever a new component is registered.
  By watching this, a component can monitor whether other components it
  depends on are available.
  """
  def __init__ (self, name, component):
    Event.__init__(self)
    self.name = name
    self.component = component

import pox.lib.recoco as recoco

class POXCore (EventMixin):
  """
  A nexus of of the POX API.

  pox.core.core is a reference to an instance of this class.  This class
  serves a number of functions.

  An important one is that it can serve as a rendezvous point for
  components.  A component can register objects on core, and they can
  then be accessed on the core object (e.g., if you register foo, then
  there will then be a pox.core.core.foo).  In many cases, this means you
  won't need to import a module.

  Another purpose to the central registration is that it decouples
  functionality from a specific module.  If myL2Switch and yourL2Switch
  both register as "switch" and both provide the same API, then it doesn't
  matter.  Doing this with imports is a pain.

  Additionally, a number of commmon API functions are vailable here.
  """
  _eventMixin_events = set([
    UpEvent,
    DownEvent,
    GoingUpEvent,
    GoingDownEvent,
    ComponentRegistered
  ])

  def __init__ (self):
    self.debug = False
    self.running = True
    self.starting_up = True
    self.components = {'core':self}

    import threading
    self.quit_condition = threading.Condition()

    self.version = (0,2,0)
    self.version_name = "carp"
    print(self.banner)

    self.scheduler = recoco.Scheduler(daemon=True)

    self._waiters = [] # List of waiting components

  @property
  def banner (self):
    return "{0} / Copyright 2011-2013 James McCauley, et al.".format(
     self.version_string)

  @property
  def version_string (self):
    return "POX %s (%s)" % ('.'.join(map(str,self.version)),self.version_name)

  def callDelayed (_self, _seconds, _func, *args, **kw):
    """
    Calls the function at a later time.
    This is just a wrapper around a recoco timer.
    """
    t = recoco.Timer(_seconds, _func, args=args, kw=kw,
                     scheduler = _self.scheduler)
    return t

  def callLater (_self, _func, *args, **kw):
    # first arg is `_self` rather than `self` in case the user wants
    # to specify self as a keyword argument
    """
    Call the given function with the given arguments within the context
    of the co-operative threading environment.
    It actually calls it sooner rather than later. ;)
    Much of POX is written without locks because it's all thread-safe
    with respect to itself, as it's written using the recoco co-operative
    threading library.  If you have a real thread outside of the
    co-operative thread context, you need to be careful about calling
    things within it.  This function provides a rather simple way that
    works for most situations: you give it a callable (like a method)
    and some arguments, and it will call that callable with those
    arguments from within the co-operative threader, taking care of
    synchronization for you.
    """
    _self.scheduler.callLater(_func, *args, **kw)

  def raiseLater (_self, _obj, *args, **kw):
    # first arg is `_self` rather than `self` in case the user wants
    # to specify self as a keyword argument
    """
    This is similar to callLater(), but provides an easy way to raise a
    revent event from outide the co-operative context.
    Rather than foo.raiseEvent(BarEvent, baz, spam), you just do
    core.raiseLater(foo, BarEvent, baz, spam).
    """
    _self.scheduler.callLater(_obj.raiseEvent, *args, **kw)

  def getLogger (self, *args, **kw):
    """
    Returns a logger.  Pass it the name you want if you'd like to specify
    one (e.g., core.getLogger("foo")).  If you don't specify a name, it
    will make one up based on the module name it is called from.
    """
    return getLogger(moreFrames=1,*args, **kw)

  def quit (self):
    """
    Shut down POX.
    """
    import threading
    if (self.starting_up or
        threading.current_thread() is self.scheduler._thread):
      t = threading.Thread(target=self._quit)
      t.daemon = True
      t.start()
    else:
      self._quit()

  def _quit (self):
    # Should probably do locking here
    if not self.running:
      return
    if self.starting_up:
      # Try again later
      self.quit()
      return

    self.running = False
    log.info("Going down...")
    import gc
    gc.collect()
    #DJ++ START 20140115
    from pox.sine.libmysql import dbop
    dbop.closedb()
    #DJ++ END 20140115
    self.raiseEvent(GoingDownEvent())
    self.callLater(self.scheduler.quit)
    for i in range(50):
      if self.scheduler._hasQuit: break
      gc.collect()
      time.sleep(.1)
    if not self.scheduler._allDone:
      log.warning("Scheduler didn't quit in time")
    self.raiseEvent(DownEvent())
    log.info("Down.")
    #logging.shutdown()
    self.quit_condition.acquire()
    self.quit_condition.notifyAll()
    core.quit_condition.release()

  def _get_python_version (self):
    try:
      import platform
      return "{impl} ({vers}/{build})".format(
       impl=platform.python_implementation(),
       vers=platform.python_version(),
       build=platform.python_build()[1].replace("  "," "))
    except:
      return "Unknown Python"