/******************************************************************************
    Copyright (c) 1998-2000 CoWare, Inc.    ALL RIGHTS RESERVED

The following code is derived, directly or indirectly, from the SystemC
source code released by Synopsys, Inc Copyright (c) 1996-2001, Synopsys,
Inc., CoWare, Inc., Frontier Design, Inc. and others. All Rights reserved.
The contents of this file are subject to the restrictions and limitations
set forth in the SystemC Open Source License Version 2.0 (the "License");
You may not use this file except in compliance with such restrictions and
limitations. You may obtain instructions on how to receive a copy of the
License at http://www.systemc.org/.  Software distributed by Contributors
under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF
ANY KIND, either express or implied. See the License for the specific
language governing rights and limitations under the License."

******************************************************************************/

/****************************************************************

    Authors : Harish Sarin, CoWare Inc. (harish@coware.com)
	    : Cesar Quiroz, CoWare Inc. (cesar@coware.com)
            : Vijay Kumar,  CoWare Inc. (vijay@coware.com)
            : Dirk Vermeersch,  CoWare Inc. (dirk@coware.com)

    MODIFICATION LOG 
    Modifier Name & Affiliation:
    Description of Modification:

****************************************************************/
#ifndef PROTOCOL_DEF_H
#define PROTOCOL_DEF_H


#include <map>

#include <vector>
#include <typeinfo>

#include <sc_string.h>


using namespace std;
using std::map;
using std::vector;


enum sc_terminal_type {TT_DATA,TT_ADDRESS,TT_CONTROL};

class SelfBindingLink;
class SelfBinding {
public:
  virtual BIND_STATUS bindIt(SelfBindingLink *link)=0;
  virtual BIND_STATUS bindIt(SelfBinding *p)=0;

};



class SelfBindingLink: public sc_rpc_link_b {
  const char * protocolType;
  std::map<int,sc_object *> datasignals;
  std::map<int,sc_object *> addresssignals;
  std::map<int,sc_object *> controlsignals;
  std::map<int,void *> attributes;
  std::map<sc_string,void *> slaveprocess;
public:
  SelfBindingLink(const char *Name) : sc_rpc_link_b(Name) {protocolType = NULL;}
  sc_object  *getSignal(int id,sc_terminal_type tt) {
    switch(tt) {
    case TT_DATA:
      if (datasignals.count(id))
	return datasignals[id];
      break;
    case TT_ADDRESS:
      if (addresssignals.count(id))
	return addresssignals[id];
      break;
    case TT_CONTROL:
      if (controlsignals.count(id))
     return controlsignals[id];
      break;
    }
    return NULL;
  }
  void setSignal(int id,sc_terminal_type tt,sc_object *o) {
    switch(tt) {
    case TT_DATA:
      datasignals[id] = o;
      break;
    case TT_ADDRESS:
      addresssignals[id] = o;
      break;
    case TT_CONTROL:
      controlsignals[id] = o;
      break;

    }
  }
  void  *getAttribute(int id) {

    if (attributes.count(id))
      return attributes[id];
    return NULL;
  }
  void setAttribute(int id,void *o) {
    attributes[id] = o;
  }
  void *getSlaves(const sc_string &name) {
    return slaveprocess[name];
  }
  void setSlaves(const sc_string &name,void *p) {
     slaveprocess[name] = p;
  }
  const char * getProtocolType() {
    return protocolType;
  }
  void setProtocolType(const char *t ) {
    protocolType = t;
  }

  void set_write_flag(bool t) { bWritten=t; }
  bool get_write_flag() { return bWritten; }
};

class SBProtocol;
template <class Tlink>
class SelfBindingLinkWithData : public SelfBindingLink {
public:
  SelfBindingLinkWithData(const char *name) :SelfBindingLink(name) {}
  Tlink& read() {	return data;}
  void write(const Tlink& val) { data=val; }
  Tlink data;
};



class SBprotocol : public sc_protocol {
public:
  static sc_protocol* instance() {
    static SBprotocol *inst = NULL;
    if (!inst)
      inst = new SBprotocol();
    return inst;
  }
  virtual sc_link_protocol* make_link_protocol(const char *NAME){
    assert(0); // shoulkd not get here
    return NULL; //;new SelfBindingLinkWithData<T>(NAME);
  }
};
class SelfBindingPortProtocol_b;
extern SelfBindingPortProtocol_b * currentPortProtocol;
extern int currentPortProtocolAddressIndex;
extern int currentPortProtocolDataIndex;
extern int currentPortProtocolControlIndex;
extern int currentAttributeIndex;



class SelfBindingPortProtocol_b :public sc_port_protocol {
public:
  SelfBindingPortProtocol_b(sc_protocol *p): sc_port_protocol(p){
    sensitive_procs = new proc_list;
    currentPortProtocol = this;
    currentPortProtocolAddressIndex = 1000000;
    currentPortProtocolDataIndex = 1000000;
    currentPortProtocolControlIndex = 1000000;
    currentAttributeIndex = 1000000;
  }
  std::vector<SelfBinding *> bindeables;
  virtual void p_add_procs(sc_async_process_handle h) {
    sensitive_procs->push_back(h);
  }
  void create_sensitivity(proc_list& list) {
    if((!sensitive_procs) || (sensitive_procs->size()==0)) {
      if(is_leaf() && !bindeables.size()) {
	cerr << "SystemC Error : Port \'"<<name()<<"' must have atleast one " <<
	  "sensitive process attached" << endl;
      }
      return;
    }
    for(int i=0; i<sensitive_procs->size(); i++) {
      sc_async_process_handle h=sensitive_procs->fetch(i);
      list.push_back(h);
    }
    delete_sensitive_procs();
  }
  void delete_sensitive_procs() {
    if (sensitive_procs) {
      sensitive_procs->erase_all();
      delete sensitive_procs;
    }
    sensitive_procs = 0;
  }
  // bind methods
  BIND_STATUS bind(sc_link_mp_b& l) {
    return sc_port_protocol::bind(l);
  }
};


template <class T>
class SelfBindingPortProtocol :public SelfBindingPortProtocol_b {
public:
  SelfBindingPortProtocol() :SelfBindingPortProtocol_b(SBprotocol::instance()) {
  }
  virtual sc_link_protocol* make_link_protocol(const char *NAME) {
    return new SelfBindingLinkWithData<T>(NAME);
  }

  SelfBindingLinkWithData<T> *link_protocol;
  virtual BIND_STATUS bind(sc_link_protocol* l) { 
    link_protocol = dynamic_cast<SelfBindingLinkWithData<T> *>(l);
    if (!link_protocol) {
      cerr << "wrong link protocol\n";
      return BIND_STATUS_WRONG_TYPE;
    }
    const char *pt = link_protocol->getProtocolType();
    if (!pt) {
       link_protocol->setProtocolType(strdup(get_port()->getProtocolType()));
    } else if (strcmp(pt,get_port()->getProtocolType())) {
      cerr << "SystemC Error: Trying to connect incompatible protocols, detected at port : \"" << name() << "\"\n";
      //return BIND_STATUS_WRONG_TYPE;

    }
    // now bind all the bindeables
    for (std::vector<SelfBinding *>:: iterator it = bindeables.begin(),last = bindeables.end();it != last;++it) 
      (*it)->bindIt(link_protocol);
    return BIND_STATUS_OK;
  }
  BIND_STATUS bind(sc_link_mp_b& l) {
    // check if the link is of the correct type
    // ask the port if we are the right type
    return SelfBindingPortProtocol_b::bind(l);
  }
  BIND_STATUS bind(SelfBindingPortProtocol<T> & p) {
    // now bind all the bindeables
    for (std::vector<SelfBinding *>:: iterator it = bindeables.begin(),last = bindeables.end(),
	   it2 = p.bindeables.begin(),last2= p.bindeables.end()
	   ;it != last && it2 != last2;
	 ++it,++it2) 
      (*it)->bindIt(*it2);
    return sc_port_protocol::bind(p);
  }
};


template <class base,class T,sc_terminal_type terminal_type, int id>
class sc_sb_port :public base,public SelfBinding {
  int idnum;
protected:
  virtual sc_signal<T> *makeSignal() {
    return new sc_signal<T> ();
  }
public:
  virtual ~sc_sb_port() {}
  sc_sb_port() {
    idnum = id;
    currentPortProtocol->bindeables.push_back(this);
    if (id <0) {
      switch (terminal_type) {
      case TT_DATA:
	idnum = currentPortProtocolDataIndex++;
	break;
      case TT_CONTROL:
	idnum = currentPortProtocolControlIndex++;
	break;
      case TT_ADDRESS:
	idnum = currentPortProtocolAddressIndex++;
	break;
      }
    }
  }
  virtual BIND_STATUS bindIt(SelfBindingLink *link) {
    sc_object *o = link->getSignal(idnum,terminal_type);
    sc_signal<T> *s;
    if (o) {
      s = dynamic_cast<sc_signal<T> * >(o);
      if (!s) {
	cerr << "error in protocol : mismatching port datatypes" << endl;
	return BIND_STATUS_WRONG_TYPE;
      }
    } else {
      s = makeSignal();
      link->setSignal(idnum,terminal_type,s);
    }
    return bind(*s);
  }
  virtual BIND_STATUS bindIt(SelfBinding *other) {
    // we need to delegate the binding 
    return vbind(dynamic_cast<const sc_port_b&>( *other));
  }
  int getId() {
    return idnum;
  }
  
};



template <class T,sc_terminal_type terminal_type=TT_DATA, int id=-1>
class sc_terminal_in :public sc_sb_port<sc_in<T>,T,terminal_type,id >{
};

template <class T,sc_terminal_type terminal_type=TT_DATA,int id=-1>
class sc_terminal_out :public sc_sb_port<sc_out<T>, T ,terminal_type,id > {
public:
  sc_terminal_out<T,terminal_type,id>& write(const T& d)
    {
      nonconst_signal()->write(d);
      return *this;
    }
  sc_terminal_out<T,terminal_type,id>& operator=(const T& d)
    {
      nonconst_signal()->write(d);
      return *this;
    }
  sc_terminal_out<T,terminal_type,id>& operator=(sc_signal<T>& sig)
    {
      nonconst_signal()->write(sig.read());
      return *this;
    }
  
};
  

template <class T,sc_terminal_type terminal_type=TT_DATA,int id=-1>
class sc_terminal_inout :public sc_sb_port<sc_inout<T>, T ,terminal_type,id > {
public:
  sc_terminal_inout<T,terminal_type,id>& write(const T& d)
    {
      nonconst_signal()->write(d);
      return *this;
    }
  sc_terminal_inout<T,terminal_type,id>& operator=(const T& d)
    {
      nonconst_signal()->write(d);
      return *this;
    }
  sc_terminal_inout<T,terminal_type,id>& operator=(sc_signal<T>& sig)
    {
      nonconst_signal()->write(sig.read());
      return *this;
    }
  sc_terminal_inout<T,terminal_type,id>& operator=(const sc_inout<T>& d)
    {
      nonconst_signal()->write(d.read());
      return *this;
    }
  
};

template <int W,sc_terminal_type terminal_type=TT_DATA, int id=-1>
class sc_terminal_in_rv :public sc_sb_port<sc_in<sc_lv<W> >,sc_lv<W> ,terminal_type,id >{
 protected:
  virtual sc_signal<sc_lv<W> > *makeSignal() {
    return new sc_signal_rv<W> ();
  }

};

template <int W,sc_terminal_type terminal_type=TT_DATA,int id=-1>
class sc_terminal_out_rv :public sc_sb_port<sc_out<sc_lv<W> >, sc_lv<W> ,terminal_type,id > {
protected:
  virtual sc_signal<sc_lv<W> > *makeSignal() {
    return new sc_signal_rv<W> ();
  }
public:
  sc_terminal_out_rv<W,terminal_type,id> & write(const sc_lv<W> & d)
    {
      nonconst_signal()->write(d);
      return *this;
    }
  sc_terminal_out_rv<W,terminal_type,id>& operator=(const sc_lv<W>& d)
    {
      nonconst_signal()->write(d);
      return *this;
    }
  sc_terminal_out_rv<W,terminal_type,id>& operator=(sc_signal<sc_lv<W> >& sig)
    {
      nonconst_signal()->write(sig.read());
      return *this;
    }
  
};
  

template <int W,sc_terminal_type terminal_type=TT_DATA,int id=-1>
class sc_terminal_inout_rv :public sc_sb_port<sc_inout<sc_lv<W> >, sc_lv<W> ,terminal_type,id > {
protected:
  virtual sc_signal<sc_lv<W> > *makeSignal() {
    return new sc_signal_rv<W> ();
  }
public:
  sc_terminal_inout_rv<W,terminal_type,id>& write(const sc_lv<W>& d)
    {
      nonconst_signal()->write(d);
      return *this;
    }
  sc_terminal_inout_rv<W,terminal_type,id>& operator=(const sc_lv<W>& d)
    {
      nonconst_signal()->write(d);
      return *this;
    }
  sc_terminal_inout_rv<W,terminal_type,id>& operator=(sc_signal<sc_lv<W> >& sig)
    {
      nonconst_signal()->write(sig.read());
      return *this;
    }
  sc_terminal_inout_rv<W,terminal_type,id >& operator=(const sc_inout<sc_lv<W> >& d)
    {
      nonconst_signal()->write(d.read());
      return *this;
    }
  
};
  
template <class T>
struct SlaveInterface : public SelfBinding,public T {
  SelfBindingLink *link;
  std::vector<T *> *slaves;
  virtual BIND_STATUS bindIt(SelfBindingLink *l) {
    void *o = l->getSlaves(typeid(T).name());
    if (!o) {
		slaves = new std::vector<T *>();
      l->setSlaves(typeid(T).name(),slaves);
    } else {
		slaves = (std::vector<T *> *)(o);
      if (!slaves) {
	cerr << "Wrong interface binding\n";
	return BIND_STATUS_WRONG_TYPE;
      }
    }
    slaves->push_back(this);
    return BIND_STATUS_OK;
  }
  
  virtual BIND_STATUS bindIt(SelfBinding *l) {
    return BIND_STATUS_OK;
  }
  
};


template <class T>
struct MasterInterface : public SelfBinding {
  MasterInterface() {
    currentPortProtocol->bindeables.push_back(this);
  }
  SelfBindingLink *link;
  std::vector<T *> *slaves;
  virtual BIND_STATUS bindIt(SelfBindingLink *l) {
//    void *o = l->getSlaves(T::getname());
    void *o = l->getSlaves(typeid(T).name());
    if (!o) {
		slaves = new std::vector<T *>();
      //l->setSlaves(T::getname(),slaves);
      l->setSlaves(typeid(T).name(),slaves);
    } else {
		slaves = (std::vector<T *> *)(o);
      if (!slaves) {
	cerr << "Wrong interface binding\n";
	return BIND_STATUS_WRONG_TYPE;
      }
    }
    return BIND_STATUS_OK;
  }
  virtual BIND_STATUS bindIt(SelfBinding *l) {
    return BIND_STATUS_OK;
  }
  
};


template <class T>
class sc_master_protocol_b:public SelfBindingPortProtocol<T> {
public:
  BIND_STATUS bind(sc_link_mp_b& l) {
    BIND_STATUS status;
    status = SelfBindingPortProtocol<T>::bind(l);
    if(BIND_STATUS_OK!=status)
      return status;
    link_protocol->masters++;
    return status;
  }
 
  BIND_STATUS bind(SelfBindingPortProtocol<T> & p) {
    return SelfBindingPortProtocol<T>::bind(p);
  }
  T & read() {
    cerr << " No read method defined for this master port protocol" << endl;
    return T();
  }
  void write(const T&) {
    cerr << " No write method defined for this master port protocol" << endl;
  }

};
template <class T>
class sc_master_abstract_protocol:public sc_master_protocol_b<T> {
public:
  void operator () (){
    link_protocol->run_slave();
  }

};


template<class Tinoutm> class sc_inoutm_abstract_protocol : public sc_master_abstract_protocol<Tinoutm> {
public:
  Tinoutm& read() {
    link_protocol->set_direction(false);
    link_protocol->run_outslave();
    link_protocol->run_inoutslave();
    return link_protocol->read();
  }
  
  void write(const Tinoutm& val) {
    link_protocol->set_write_flag(false);
    link_protocol->write(val);
    link_protocol->set_direction(true);
    link_protocol->run_inoutslave();
    link_protocol->run_inslave();
  }
};

template <class Toutm> 
class sc_outm_abstract_protocol : public sc_master_abstract_protocol<Toutm> {
public:
  void write(const Toutm& val) {
    link_protocol->write(val);
    link_protocol->set_direction(true);
    link_protocol->run_inoutslave();
    link_protocol->run_inslave();
  }
};


template<class Tinm> class sc_inm_abstract_protocol : public sc_master_abstract_protocol<Tinm>  {
public:
  Tinm& read() {
    link_protocol->set_write_flag(false);
    link_protocol->set_direction(false);
    link_protocol->run_inoutslave();
    link_protocol->run_outslave();
    return link_protocol->read();
  }
};

template<class Tslave> class sc_slave_protocol_b : public SelfBindingPortProtocol<Tslave> {
public:
  // bind methods
  BIND_STATUS bind(sc_link_mp_b& l) {
    BIND_STATUS status;
    status = SelfBindingPortProtocol<Tslave>::bind(l);
    if(BIND_STATUS_OK!=status)
      return status;
    link_protocol->slaves++;
    return status;
  }
  BIND_STATUS bind(SelfBindingPortProtocol<Tslave> & p) {
    return SelfBindingPortProtocol<Tslave>::bind(p);
  }
  Tslave & read() {
    Tslave t;
    cerr << " No read method defined for this master port protocol" << endl;
    return t;
  }
  void write(const Tslave&) {
    cerr << " No write method defined for this master port protocol" << endl;
  }
  
  virtual void make_sensitive() {
    if ( (sensitive_procs) && (sensitive_procs->size() ) ) {
    cerr << "SystemC error : slave processes only allowed on rpc derived protocols" << endl;
    }
  }
};
template<class Tslave> class sc_slave_abstract_protocol : public sc_slave_protocol_b<Tslave> {
public:
  virtual void make_sensitive() {
    //link_protocol can be '0' due to incompatible protocols
    if(link_protocol)
      create_sensitivity(link_protocol->slaveList);
  }
};

template<class Tinouts> class sc_inouts_abstract_protocol : public sc_slave_abstract_protocol<Tinouts> {
public:
  Tinouts& read() { return link_protocol->read(); }
  void write(const Tinouts& val) {
    if(link_protocol->get_write_flag()) {
      cerr<<"SystemC Error: `"<<name()<<
	"\': Cannot write twice to the same link "<<endl;
      return;
    }
    link_protocol->write(val);
    link_protocol->set_write_flag(false);
  }
  bool input() { return link_protocol->get_direction(); }
  virtual void make_sensitive() {
    //link_protocol can be '0' due to incompatible protocols
    if(link_protocol)
      create_sensitivity(link_protocol->inoutSlaveList);
  }
};
	
/////////////////////////////////////////////////////////////////////////
// sc_inslave will loook for this class in every protocol implementation
template<class Tins> class sc_ins_abstract_protocol : public sc_slave_abstract_protocol<Tins> {
public:
  Tins& read() { return link_protocol->read(); }
  virtual void make_sensitive() {
    //link_protocol can be '0' due to incompatible protocols
    if(link_protocol)
      create_sensitivity(link_protocol->inSlaveList);
  }
};


/////////////////////////////////////////////////////////////////////////
// sc_outslave will loook for this class in every protocol implementation
template<class Touts> class sc_outs_abstract_protocol : public sc_slave_abstract_protocol<Touts> {
public:
  void write(const Touts& val) {
    if(link_protocol->get_write_flag()) {
      cerr<<"SystemC Error: `"<<name()<<
	"\': Cannot write twice to the same link "<<endl;
      return;
    }
    link_protocol->write(val);
    link_protocol->set_write_flag(false);
  }
  virtual void make_sensitive() {
    //link_protocol can be '0' due to incompatible protocols
    if(link_protocol)
      create_sensitivity(link_protocol->outSlaveList);
  }
};







#define SC_INTERFACE(T) \
   master_##T T


#define SC_OUTMASTER_RPC struct sc_outm_protocol : public  sc_outm_abstract_protocol<datatype>

#define SC_INMASTER_RPC struct sc_inm_protocol : public  sc_inm_abstract_protocol<datatype>

#define SC_MASTER_RPC  struct sc_master_protocol : public sc_master_abstract_protocol<datatype>

#define SC_INOUTMASTER_RPC  struct sc_inoutm_protocol : public  sc_inoutm_abstract_protocol<datatype>

#define SC_OUTSLAVE_RPC struct sc_outs_protocol : public  sc_outs_abstract_protocol<datatype>

#define SC_INSLAVE_RPC  struct sc_ins_protocol : public  sc_ins_abstract_protocol<datatype>

#define SC_SLAVE_RPC  struct sc_slave_protocol : public sc_slave_abstract_protocol<datatype>

#define SC_INOUTSLAVE_RPC  struct sc_inouts_protocol : public  sc_inouts_abstract_protocol<datatype>



#define SC_OUTMASTER_P struct sc_outm_protocol : public  sc_master_protocol_b<int>

#define SC_INMASTER_P struct sc_inm_protocol : public  sc_master_protocol_b<int>

#define SC_MASTER_P  struct sc_master_protocol : public sc_master_protocol_b<int>

#define SC_INOUTMASTER_P  struct sc_inoutm_protocol : public  sc_master_protocol_b<int>

#define SC_OUTSLAVE_P struct sc_outs_protocol : public  sc_slave_protocol_b<int>

#define SC_INSLAVE_P  struct sc_ins_protocol : public  sc_slave_protocol_b<int>

#define SC_SLAVE_P  struct sc_slave_protocol : public sc_slave_protocol_b<int>

#define SC_INOUTSLAVE_P  struct sc_inouts_protocol : public  sc_slave_protocol_b<int>


#define SC_PROTOCOL(name) struct name : public SBprotocol


#define SC_INTERFACE_DEF(name,args,argnames) \
     struct name##_interface { \
     virtual void name args = 0;\
};   \
struct master_##name : public MasterInterface<name##_interface> { \
  void operator()args { \
  for (std::vector<name##_interface *>:: iterator it = slaves->begin(),last = slaves->end();it != last;++it) \
      (*it)->name argnames;\
  } \
} ; \
template <class T> \
struct slave_delegate_##name : public SlaveInterface<name##_interface> { \
  typedef void (T::*f_type) args ; \
  f_type delegate ; \
  T * module; \
  slave_delegate_##name(T*m,f_type d) : module(m) { delegate= d; }  \
  void name args { \
    (module->*delegate) argnames; \
  } \
};\



#define SC_IMPLEMENTS(port,name,impl) \
port->bindeables.push_back(new slave_delegate_##name<SC_CURRENT_USER_MODULE>(this,&SC_CURRENT_USER_MODULE::impl));


template <class T> 
SC_PROTOCOL(interface_protocol) {
  SC_MASTER_P ,public T {
  };
  SC_SLAVE_P {
  };

};


/* attributes */

template <class T,int id>
class sc_attribute : public SelfBinding {
protected:
  int idnum;
  T *value;
public:
  virtual ~sc_attribute() {}
  sc_attribute() {
    idnum = id;
    currentPortProtocol->bindeables.push_back(this);
    if (id <0) {
      idnum= currentAttributeIndex++;
    }
  }
  virtual BIND_STATUS bindIt(SelfBindingLink *link) {
    T *s = (T *)link->getAttribute(idnum);
    if (!s) {
      s = new T();
      link->setAttribute(idnum,s);
    }
    value = s;
  }
  virtual BIND_STATUS bindIt(SelfBinding *l) {
    return BIND_STATUS_OK;
  }
  int getId() {
    return idnum;
  }
  T * operator ->() { return value; }
};



template <class T,int id=-1>
class sc_attribute_in : public sc_attribute<T,id>
{
public:
  const T& read() {
    return *value;
  }
  operator const T& () {return read(); }
};


template <class T,int id=-1>
class sc_attribute_out : public sc_attribute<T,id>
{
public:
  void write(const T&v) {
    *value = v;
  }
  sc_attribute_out<T,id>& operator =(const T& val) {
    write(val);
    return *this;
  }
};

template <class T,int id=-1>
class sc_attribute_inout : public sc_attribute<T,id>
{
public:
  void write(const T&v) {
    *value = v;
  }
  sc_attribute_out<T,id>& operator =(const T& val) {
    write(val);
    return *this;
  }
  const T& read() {
    return *value;
  }

  operator const T& () {return read(); }
};

#endif
