/** @file eca.h
 *  @brief C++ Interface to the ECA hardware.
 *  @author Wesley W. Terpstra <w.terpstra@gsi.de>
 *
 *  Copyright (C) 2013 GSI Helmholtz Centre for Heavy Ion Research GmbH 
 *
 *  Public API to control all aspects of the Event-Condition-Action Unit.
 *
 *******************************************************************************
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 3 of the License, or (at your option) any later version.
 *
 *  This library 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
 *  Lesser General Public License for more details.
 *  
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library. If not, see <http://www.gnu.org/licenses/>.
 *******************************************************************************
 */

#ifndef ECA_H
#define ECA_H

#include <etherbone.h>
#include <string>
#include <vector>
#include <list>

namespace GSI_ECA {

using namespace etherbone;

typedef uint8_t  Channel;
typedef uint64_t Event;
typedef int16_t  Index;
typedef uint64_t Param;
typedef uint32_t Tag;
typedef uint32_t Tef;
typedef uint64_t Time;

/* An Event sent to the ECA has these fields */
struct EventEntry {
  Event event;
  Param param;
  Tef   tef;
  Time  time;
  
  EventEntry(Event e = 0, Param p = 0, Tef t = 0, Time i = 0)
   : event(e), param(p), tef(t), time(i) { }
};

enum ActionStatus {
  VALID = 0,
  CONFLICT,
  LATE
};

/* An action queued to be executed has these fields */
struct ActionEntry {
  Event event;
  Param param;
  Tag   tag;
  Tef   tef;
  Time  time;
  ActionStatus status;
  
  ActionEntry(Event e = 0, Param p = 0, Tag a = 0, Tef t = 0, Time i = 0, ActionStatus s = VALID)
   : event(e), param(p), tag(a), tef(t), time(i), status(s) { }
};

/* Software condition table entry fields */
struct TableEntry {
  Event   event;
  Time    offset;
  Tag     tag;
  Channel channel;
  uint8_t event_bits;
  
  TableEntry(Event e = 0, Time o = 0, Tag t = 0, Channel c = 0, uint8_t b = 0)
   : event(e), offset(o), tag(t), channel(c), event_bits(b) { }
};

/* Condition table */
class Table {
  private:
    struct Impl;
    Impl *impl;
    
  public:
    Table();
    Table(const Table& table);
    ~Table();
    
    void swap(Table& table);
    Table& operator = (Table x);
    
    int add(const TableEntry& te); /* Returns # records overwritten (conflict) */
    int del(const TableEntry& te); /* Returns # records removed/modified */
    
    /* Bulk import/export of entries */
    int  set(const std::vector<TableEntry>& vect);
    void get(std::vector<TableEntry>& vect) const;
  
  friend struct ECA;
};


/* ======================================================================= */
/* Software interface to hardware action queues                            */
/* ======================================================================= */
struct ActionQueue {
  /* ------------------------------------------------------------------- */
  /* Constant hardware values                                            */
  /* ------------------------------------------------------------------- */
  uint8_t     sdb_ver_major;
  uint8_t     sdb_ver_minor;
  uint32_t    sdb_version;
  uint32_t    sdb_date;
  std::string sdb_name;
  
  uint16_t     queue_size;
  
  /* ------------------------------------------------------------------- */
  /* Mutable hardware registers; only modify using methods below         */
  /* ------------------------------------------------------------------- */
  bool         arrival_enable;  /* Enable arrival interrupt delivery */
  uint32_t     arrival_dest;    /* Destination for arrival interrupt */
  bool         overflow_enable; /* Enable overflow interrupt delivery */
  uint32_t     overflow_dest;   /* Destination for overflow interrupt */
  uint32_t     queued_actions;  /* # of actions queued to be read */
  uint32_t     dropped_actions; /* # of actions dropped due to overflow */
  
  /* ------------------------------------------------------------------- */
  /* Access/modify the underlying hardware                               */
  /* ------------------------------------------------------------------- */
  
  Device       device;       /* Device which hosts this ECA */
  eb_address_t address;      /* Wishbone base address */
  
  /* Reload mutable registers from hardware */
  status_t refresh(); 
  /* Clear all dropped counter */
  status_t reset();
  
  /* Hook/unhook interrupt handling */
  status_t hook_arrival (bool enable, uint32_t dest);
  status_t hook_overflow(bool enable, uint32_t dest);
  
  /* Pop the next queued action from the queue */
  status_t pop(ActionEntry& queue);
};

/* ======================================================================= */
/* Software interface to hardware action channels                          */
/* ======================================================================= */
struct ActionChannel {
  /* ------------------------------------------------------------------- */
  /* Constant hardware values                                            */
  /* ------------------------------------------------------------------- */
  std::string  name;         /* Channel instance name */
  unsigned     queue_size;   /* Size of the inspectable queue */
  
  std::list<ActionQueue> queue; /* Only present if an AQ is connected */
  
  /* ------------------------------------------------------------------- */
  /* Mutable hardware registers; only modify using methods below         */
  /* ------------------------------------------------------------------- */
  bool         draining;   /* Queue is being erased; nothing enters/exits */
  bool         frozen;     /* Queue is frozen;       nothing enters/exits */
  bool         int_enable; /* Are interrupts for this channel enabled? */
  uint32_t     int_dest;   /* Destination for interrupts */
  uint16_t     fill;       /* Current number of entries in the queue */
  uint16_t     max_fill;   /* Maximum entries in the queue since reset */
  uint32_t     valid;      /* How many valid actions have been sent (includes late+conflict) */
  uint32_t     conflict;   /* How many conflicting actions have been sent */
  uint32_t     late;       /* How many late  actions have been sent */
  
  /* ------------------------------------------------------------------- */
  /* Access/modify the underlying hardware                               */
  /* ------------------------------------------------------------------- */
  
  Device       device;       /* Device which hosts this ECA */
  eb_address_t address;      /* Wishbone base address */
  Channel      index;        /* Index of the channel */
  
  /* Reload drain, freeze, fill, max_fill from hardware. */
  status_t refresh(); 
  /* Clear all counters (max_fill, valid, late) */
  status_t reset();
  
  /* Toggle queue states */
  status_t freeze(bool freeze);
  status_t drain (bool drain);
  
  /* Hook/unhook interrupt handling */
  status_t hook(bool enable, uint32_t address);
  
  /* Grab the contents from a frozen channel */
  status_t load(std::vector<ActionEntry>& queue);
};

/* ======================================================================= */
/* Software interface to hardware event streams                            */
/* ======================================================================= */
struct EventStream {
  /* ------------------------------------------------------------------- */
  /* Constant hardware values                                            */
  /* ------------------------------------------------------------------- */
  uint8_t     sdb_ver_major;
  uint8_t     sdb_ver_minor;
  uint32_t    sdb_version;
  uint32_t    sdb_date;
  std::string sdb_name;
  
  /* ------------------------------------------------------------------- */
  /* Access/modify the underlying hardware                               */
  /* ------------------------------------------------------------------- */
  
  Device       device;  /* Device which hosts this ECA */
  eb_address_t address; /* Base address of the event stream */
  
  /* Send an event to the stream */
  status_t send(EventEntry e);
};

/* ======================================================================= */
/* Software interface to the ECA hardware                                  */
/* ======================================================================= */
struct ECA {
  /* ------------------------------------------------------------------- */
  /* Constant hardware values                                            */
  /* ------------------------------------------------------------------- */
  
  std::string  name;         /* ECA instance name */
  
  uint8_t     sdb_ver_major; /* API version; major.minor */
  uint8_t     sdb_ver_minor;
  uint32_t    sdb_version;   /* Implementation version */
  uint32_t    sdb_date;      /* Implementation change-date */
  std::string sdb_name;      /* Device category name */
  
  bool inspect_table;        /* Hardware can inspect condition table */
  bool inspect_queue;        /* Hardware can inspect queue contents  */
  
  unsigned table_size;       /* Walker table size, 1/2 search size */
  unsigned queue_size;       /* Maximum actions which can be queued */
  
  uint32_t freq_mul;         /* ECA operating frequency =   */
  uint8_t  freq_5s;          /*  freq_mul*5^freq_5s*2^freq_2s/freq_div */
  uint8_t  freq_2s;
  uint16_t freq_div;
  
  std::vector<ActionChannel> channels; /* Slave channel hardware */
  std::vector<EventStream>   streams;  /* Slave stream hardware  */
  
  /* ------------------------------------------------------------------- */
  /* Mutable hardware registers; only modify using methods below         */
  /* ------------------------------------------------------------------- */
  
  Time time;       /* Time as of the last refresh */
  bool disabled;   /* When disabled, incoming events are dropped */
  bool interrupts; /* Gnerate interrupts? See also ActionChannel.int_enable */
  
  /* ------------------------------------------------------------------- */
  /* Translate hardware parameters into software-friendly form           */
  /* ------------------------------------------------------------------- */
  
  /* Pretty print the ECA operating frequency */
  std::string frequency(void) const;
  
  /* Format the time/frequency into a date. */
  std::string date(Time time = (Time)-1) const;
  
  /* Transform a delay (in seconds) into an ECA time offset */
  uint64_t delay(double seconds) const;
  double   delay(uint64_t seconds) const;
  
  /* Calculate time/frequency exactly:
   *  seconds    = floor(time/frequency)
   *  subseconds = time/frequency - seconds  ... range=[0, 1)
   */
  void seconds(uint64_t* seconds, double* subseconds, Time t = (Time)-1) const;
  
  /* ------------------------------------------------------------------- */
  /* Access/modify the underlying hardware                               */
  /* ------------------------------------------------------------------- */
  
  Device       device;       /* Device which hosts this ECA */
  eb_address_t address;      /* Wishbone base address */
  unsigned     index;        /* Index of the ECA */
  
  status_t refresh(); /* refresh time+disabled */
  
  status_t disable  (bool disabled);   /* Enable/disable the ECA unit */
  status_t interrupt(bool interrupts); /* Enable/disable interrupts */
  status_t flipTables();               /* Atomicly flip inactive and active tables  */
  
  /* Load/store the condition table */
  status_t load(bool active, Table& table);
  status_t store(const Table& table);
  
  /* Locate all the ECA units on the bus */
  static status_t probe(Device dev, std::vector<ECA>& ecas);
};


/* ======================================================================= */
/* Inline functions that are not part of the ABI                           */
/* ======================================================================= */

inline void Table::swap(Table& table) {
  Impl* tmp = impl;
  impl = table.impl;
  table.impl = tmp;
}

inline Table& Table::operator = (Table x) {
  swap(x);
  return *this;
}

}

#endif
