/* LICENSE:
  =========================================================================
    CMPack'03 Source Code Release for OPEN-R SDK v1.0
    Copyright (C) 2003 Multirobot Lab [Project Head: Manuela Veloso]
    School of Computer Science, Carnegie Mellon University
    All rights reserved.
  ========================================================================= */

#include "../headers/DogTypes.h"
#include "../headers/CircBufPacket.h"
#include "../headers/FileSystem.h"
#include <iostream>

#include <map>
#include <set>
#include <string>
#include <vector>

using namespace std;

#include "Events.h"

static const bool debug_event_manager = false;

// ===== EventList implementation =========================================== //

bool EventList::present(uint event_id) const
{
  EventList::const_iterator cur_iter,end_iter;

  end_iter = end();
  for(cur_iter=begin(); cur_iter!=end_iter; cur_iter++){
    if(*cur_iter == event_id)
      return true;
  }

  return false;
}

EventList::iterator EventList::begin()
{
  return events.begin();
}

EventList::const_iterator EventList::begin() const
{
  return events.begin();
}

EventList::iterator EventList::end()
{
  return events.end();
}

EventList::const_iterator EventList::end() const
{
  return events.end();
}

// ===== EventProcessor implementation======================================= //

bool EventProcessor::initConnections()
{
  return true;
}

bool EventProcessor::update(ulong time,const EventList *events)
{
  return false;
}

// ===== EventCacheTracker implementation=================================== //

EventCacheTracker::EventCacheTracker() :
  available_time(0UL),
  cache_time(0UL)
{
}

void EventCacheTracker::updateAvailable(ulong time)
{
  available_time = time;
}

bool EventCacheTracker::shouldUpdate(ulong requested_time)
{
  return (requested_time >= available_time && available_time > cache_time);
}

void EventCacheTracker::cacheUpdated(ulong time)
{
  cache_time = time;
}

// ===== PendingEventOrderer implementation ================================ //

PendingEventOrderer::PendingEventOrderer(std::map<uint,uint> *_id_order) :
  id_order(_id_order)
{
}

PendingEventOrderer::PendingEventOrderer(const PendingEventOrderer &x)
{
  id_order = x.id_order;
}

bool PendingEventOrderer::operator()(PendingEvent * const &x,PendingEvent * const &y) const
{
  int x_id,y_id;
  int x_order,y_order;

  x_id = x->id_to_update;
  y_id = y->id_to_update;

  x_order = (*id_order)[x_id];
  y_order = (*id_order)[y_id];

  return x_order > y_order;
}

// ===== EventManager implementation ======================================== //

EventManager::EventManager() :
  event_queue(PendingEventOrderer(&id_order))
{
  num_ids = 0;
  read_implementation_names = false;
  id_order_valid = false;
}

EventManager *EventManager::getManager()
{
  static EventManager *mgr=new EventManager;

  return mgr;
}

void EventManager::addEventProcessor(std::string name,EventProcessor::EventProcessorGenerator event_proc_gen)
{
  uint id;
  id = num_ids;
  num_ids++;

  cout << "registering event processor with name '" << name << "'" << endl;

  ids[name] = id;
  // make extra entries to fill out to large enough size.  new elements are assigned NULL
  if(event_proc_gens.size() < num_ids){
    event_proc_gens. insert(event_proc_gens.end(), num_ids - event_proc_gens.size(), NULL);
    event_proc_names.insert(event_proc_names.end(),num_ids - event_proc_names.size(),std::string(""));
  }
  event_proc_gens [id] = event_proc_gen;
  event_proc_names[id] = name;
}

void EventManager::ensureImplementationNamesRead()
{
  if(read_implementation_names)
    return;

  read_implementation_names = true;

  HFS::FILE *impl_cfg_file;

  impl_cfg_file = HFS::fopen("/MS/config/implemnt.cfg","rt");


  if(impl_cfg_file == NULL)
    return;

  char buf[1024];
  while(HFS::fgets(buf,sizeof(buf),impl_cfg_file)!=NULL){
    bool comment=false; // comment and/or white space only
    bool done;
    char *cur_pos;

    done = false;
    for(cur_pos=buf; !done && *cur_pos!='\00'; cur_pos++){
      switch(*cur_pos){
        case ' ':
        case '\t': 
        case '\n':
          break;
        case '#':
          comment = true;
          done = true;
          break;
        default:
          comment = false;
          done = true;
          break;
      }
    }

    if(comment){
      cout << "no non-whitespace/comments found in line '" << buf << "'" << endl;
      //pprintf(TextOutputStream,"no non-whitespace/comments found in line '%s'\n",buf);
      continue;
    }

    char virtual_name[1024];
    char impl_name[1024];
    if(sscanf(buf," %s %s ",virtual_name,impl_name)==2){
      implementation_names[std::string(virtual_name)] = std::string(impl_name);
    }else{
      cout << "error parsing implemnt.cfg line '" << buf << "', ignoring" << endl;
      //pprintf(TextOutputStream,"error parsing run.cfg line '%s', ignoring\n",buf);
    }
  }

  HFS::fclose(impl_cfg_file);  
}

uint EventManager::getEventProcessorId(std::string name)
{
  ensureImplementationNamesRead();

  std::string impl_name;
  std::map<std::string, std::string>::iterator impl_name_iter;
  impl_name = name;
  impl_name_iter = implementation_names.find(name);
  if(impl_name_iter != implementation_names.end()){
    impl_name = (*impl_name_iter).second;
  }

  uint id;
  map<std::string, uint>::iterator id_map_iter;
  
  id_map_iter = ids.find(impl_name);
  if(id_map_iter == ids.end()){
    cout << "couldn't find event processor with name '" << impl_name << "'" << endl;
    return ~0U;
  }
  id = (*id_map_iter).second;

  bool exists;
  exists = ((id < event_procs.size()) && (event_procs[id] != NULL));

  if(!exists){
    EventProcessor::EventProcessorGenerator gen;
    EventProcessor *proc;
    gen = event_proc_gens[id];
    if(gen == NULL){
      cout << "generator is null for event processor with name '" << impl_name << "'" << endl;
      return ~0UL;
    }
    proc = (*gen)();
    if(event_procs.size() < id+1){
      event_procs.insert(event_procs.end(),id+1 - event_procs.size(),NULL);
    }
    event_procs[id] = proc;

    //bool ok=true;
    bool ok=proc->initConnections();
    if(!ok){
      event_procs[id] = NULL;
      delete proc;
    }
  }

  return id;
}

EventProcessor *EventManager::getEventProcessor(uint id)
{
  return (id==~0U ? NULL : event_procs[id]);
}

void EventManager::addConnection(uint id_from,uint id_to)
{
  bool exists=false; // whether connection exists

  ConnectionMap::iterator conn_iter1,conn_iter2;

  getConnections(id_from,conn_iter1,conn_iter2);
  for(; !exists && conn_iter1!=conn_iter2; conn_iter1++){
    if((*conn_iter1).second == id_to)
      exists = true;
  }

  if(!exists){
    connections.insert(make_pair(id_from,id_to));
    id_order_valid = false;
  }
}

EventProcessor *EventManager::listenEventProcessor(uint id_listener,uint id_talker)
{
  if(id_talker==~0U)
    return NULL;

  addConnection(id_talker,id_listener);
  
  return event_procs[id_talker];
}

void EventManager::updateIdOrder(uint id,uint &time,set<uint> &ids_seen)
{
  // have seen this node
  ids_seen.insert(id);

  // process all nodes that are after this one

  ConnectionMap::iterator ids_to1,ids_to2;

  getConnections(id,ids_to1,ids_to2);
  for(; ids_to1!=ids_to2; ids_to1++){
    int id_to;
    
    id_to = (*ids_to1).second;
    if(ids_seen.find(id_to) != ids_seen.end())
      continue;
    
    updateIdOrder(id_to,time,ids_seen);
  }

  // label this node
  id_order[id] = time++;
}

void EventManager::updateIdOrder()
{
  id_order.clear();

  set<uint> ids_seen;
  uint next_order_num;

  next_order_num = 0;

  for(int id=min(num_ids-1,event_procs.size()-1); id>=0; id--){
    // only expand nodes that are actually being used by evidence of their being instantiated
    if(event_procs[id] != NULL){
      // skip nodes we have already seen
      if(ids_seen.find(id) != ids_seen.end())
        continue;

      // process this node
      updateIdOrder(id,next_order_num,ids_seen);
    }
  }

  id_order_valid = true;
}

void EventManager::ensureIdOrderValid()
{
  if(!id_order_valid){
    updateIdOrder();
  }
}

void EventManager::injectEvent(uint id_to_update)
{
  if(id_to_update >= num_ids)
    return;

  ensureIdOrderValid();

  PendingEvent event;
  PendingEvent *pending_event;
  PendingEventQueue::iterator event_iter;
  event.id_to_update = id_to_update;
  event_iter = event_queue.find(&event);
  if(event_iter == event_queue.end()){
    pending_event = new PendingEvent;
    pending_event->id_to_update = id_to_update;
    if(debug_event_manager)
      pprintf(TextOutputStream,"injecting event for id %d ('%s')\n",id_to_update,event_proc_names[id_to_update].c_str());
    event_queue.insert(pending_event);
  }
}

void EventManager::propagateEvents(ulong time)
{
  // ensure validity of id_order
  ensureIdOrderValid();

  //// print out id_order
  //pprintf(TextOutputStream,"id order:\n");
  //for(std::map<uint,uint>::iterator iter=id_order.begin(); iter!=id_order.end(); iter++){
  //  uint id_num,order_num;
  //
  //  id_num    = (*iter).first;
  //  order_num = (*iter).second;
  //
  //  pprintf(TextOutputStream,"ido: %2u ('%s') -> %2u\n",id_num,event_proc_names[id_num].c_str(),order_num);
  //}

  // loop until no more pending updates
  while(!event_queue.empty()) {
    // get a structurally earliest pending update from queue
    PendingEvent *pending_event;
    pending_event = *event_queue.begin();
    event_queue.erase(event_queue.begin());

    // call update function with list of pending events
    EventProcessor *proc;
    bool new_data;
    uint id_to_update;
    id_to_update = pending_event->id_to_update;
    proc = event_procs[id_to_update];
    if(debug_event_manager)
      pprintf(TextOutputStream,"processing event for id %d ('%s')\n",id_to_update,event_proc_names[id_to_update].c_str());
    new_data = proc->update(time,&pending_event->events);

    // FIXME: dynamic allocation/deallocation stinks
    delete pending_event;

    // if update generated an event, ensure a pending update on all listeners
    //   and add a update event to each pending update
    if(new_data){
      ConnectionMap::iterator id_to1,id_to2;
      getConnections(id_to_update,id_to1,id_to2);

      for(; id_to1!=id_to2; id_to1++){
        uint id_to;

        id_to = (*id_to1).second;
        PendingEvent event;
        PendingEventQueue::iterator event_iter;
        event.id_to_update = id_to;
        event_iter = event_queue.find(&event);
        if(event_iter == event_queue.end()){
          pending_event = new PendingEvent;
          pending_event->id_to_update = id_to;
          if(debug_event_manager)
            pprintf(TextOutputStream,"adding event for id %d ('%s')\n",id_to,event_proc_names[id_to].c_str());
          event_queue.insert(pending_event);
        }else{
          pending_event = *event_iter;
        }

        if(debug_event_manager)
          pprintf(TextOutputStream,"adding event from id %d to id %d ('%s')\n",id_to_update,id_to,event_proc_names[id_to].c_str());
        pending_event->events.add(id_to_update);
      }
    }
  }

  if(debug_event_manager)
    pprintf(TextOutputStream,"no more events to propagate\n");
}
