#ifndef CACHE_H
#define CACHE_H

#include <stdint.h>
#include <stdarg.h>
#include <iostream>
#include "cache_multilevel.h"
#include "cache_onelevel.h"
#include "type_req_cache.h"
#include "type_rsp_cache.h"

using namespace std;
using namespace hierarchy_memory::cache::cache_multilevel;


namespace hierarchy_memory          {
  namespace cache                     {
    typedef enum
      {
	INSTRUCTION_CACHE ,
	DATA_CACHE 
      } cache_t;

    class param_t
    {
    public : const char                * name                  ;
    public : uint32_t                    nb_entity             ;
    public : cache_multilevel::param_t * param_icache_dedicated; // 1 each entity
    public : cache_multilevel::param_t * param_dcache_dedicated; // 1 each entity
    public : cache_multilevel::param_t   param_cache_shared    ; // 1 to all entity and I/D is unify
  
    public : param_t () {};

    public : param_t (const char                * name                  ,
		      uint32_t                    nb_entity             ,
		      cache_multilevel::param_t * param_icache_dedicated,
		      cache_multilevel::param_t * param_dcache_dedicated,
		      cache_multilevel::param_t   param_cache_shared    
		      ) :
      name (name)
      {
	this->name                   = name                    ;
	this->nb_entity              = nb_entity               ;
	this->param_icache_dedicated = param_icache_dedicated  ;
	this->param_dcache_dedicated = param_dcache_dedicated  ;
	this->param_cache_shared     = param_cache_shared      ;
      }
    
      friend ostream& operator<< (ostream& output_stream, const param_t & x)
      {
	output_stream << "<" << x.name << ">" << endl
		      << "  * nb_entity : " << x.nb_entity << endl;

	for (uint32_t it = 0; it < x.nb_entity; it ++)
	  output_stream << x.param_icache_dedicated [it] << endl;
	for (uint32_t it = 0; it < x.nb_entity; it ++)
	  output_stream << x.param_dcache_dedicated [it] << endl;
      
	output_stream << x.param_cache_shared << endl;
      
	return output_stream;
      }
   
    };//end param_t


    class Cache
    {
    private  : char              * name            ;
    protected: const uint32_t      nb_entity       ;
    protected: uint32_t            nb_iport        ;
    protected: uint32_t            nb_dport        ;

    protected: Cache_Multilevel ** icache_dedicated;
    protected: Cache_Multilevel ** dcache_dedicated;
    protected: Cache_Multilevel  * cache_shared    ;

      //*****[ constructor ]*****
    public   : Cache (param_t param):
      nb_entity (param.nb_entity)
      {
	uint32_t size_name = strlen(param.name)+1;
	name               = new char [size_name];
	strncpy(name,param.name,size_name);
	
	cache_shared     = new Cache_Multilevel (param.param_cache_shared);
	icache_dedicated = new Cache_Multilevel * [nb_entity];
	dcache_dedicated = new Cache_Multilevel * [nb_entity];
      
	nb_iport         = 0;
	nb_dport         = 0;
	for (uint32_t it = 0; it < nb_entity        ; it++)
	  {
	    icache_dedicated [it] = new Cache_Multilevel (param.param_icache_dedicated[it]); 
	    dcache_dedicated [it] = new Cache_Multilevel (param.param_dcache_dedicated[it]); 
	    nb_iport             += param.param_icache_dedicated[it].nb_port;
	    nb_dport             += param.param_dcache_dedicated[it].nb_port;
	  }

	information();
      };

      //*****[ destructor ]*****
    public   : ~Cache ()
      {
	for (uint32_t it = 0; it < nb_entity        ; it++)
	  {
	    delete icache_dedicated      [it];
	    delete dcache_dedicated      [it];
	  }
	delete cache_shared          ;
      };

      //*****[ reset ]*****
      void reset ()
      {
	cache_shared -> reset();
	// Reset buffer of respons
	for (uint32_t it = 0; it < nb_entity; it++)
	  {
	    icache_dedicated [it] ->reset();
	    dcache_dedicated [it] ->reset();
	  }
      }//end reset

      //*****[ transition ]*****
      void transition()
      {
	cache_shared -> transition();
	// Transition buffer of respons
	for (uint32_t it = 0; it < nb_entity; it++)
	  {
	    icache_dedicated      [it] ->transition();
	    dcache_dedicated      [it] ->transition();
	  }
      }//end transition

      //*****[ range_port ]*****
    public : uint32_t range_port (cache_t               type_cache, 
				  uint32_t              num_entity)
      {
	uint32_t nb_port_dedicated = 0;
      
	if (type_cache == INSTRUCTION_CACHE)
	  for (uint32_t it = 1; it < num_entity; it++ )
	    nb_port_dedicated += icache_dedicated [it]->nb_port;
	else
	  {
	    nb_port_dedicated = nb_iport;
	    for (uint32_t it = 1; it < num_entity; it++)
	      nb_port_dedicated += dcache_dedicated [it]->nb_port;
	  }
	return nb_port_dedicated;
      }
    
      //*****[ latence ]*****
      // Return the time to have the data
      // latence is call on the same port in a single cycle, only the last access is save
      // { NOTE } : type_cache = 0 : icache, 1 : dcache
    public : uint32_t latence (cache_t               type_cache, 
			       uint32_t              num_entity, 
			       uint32_t              num_port  , 
			       uint32_t              address   , 
			       uint32_t              trdid     , 
			       type_req_cache_t      type      ,
			       direction_req_cache_t dir    )
      {
	if (num_entity >= nb_entity)
	  {
	    cerr << "<Cache.latence> {ERROR} num_entity ("<< num_entity << ") can be >= nb_entity (" << nb_entity << ")" << endl;
	    exit (1);
	  }

	Cache_Multilevel * cache;
	if (type_cache == INSTRUCTION_CACHE)
	  cache = icache_dedicated [num_entity];
	else
	  cache = dcache_dedicated [num_entity];
      
	if (num_port >= cache->nb_port)
	  {
	    cerr << "<Cache.latence> {ERROR} num_port ("<< num_port << ") can be >= nb_port (" << cache->nb_port << ")" << endl;
	    exit (1);
	  }
	// Make a access with this level "dedicated"

	access_t access_dedicated = cache->access(num_port,address,trdid,type,dir);
 
	// cout << "access_dedicated         : " << access_dedicated << endl;

	if (access_dedicated.hit == MISS)
	  {
	    // Make a access with this level "shared"
	    access_t access_shared  = cache_shared->access(range_port (type_cache,num_entity)+num_port,address,trdid,type,dir);
	    // cout << "access_shared            : " << access_shared    << endl;

	    cache_shared->update_access (access_shared);

	    access_dedicated.last_nb_level = cache->nb_level; // Update all cache
	    access_dedicated.latence += access_shared.latence;

	    // cout << "access_dedicated (after) : " << access_dedicated << endl;
	  }
      
	cache->update_access (access_dedicated);
	return access_dedicated.latence;
      }

      //*****[ information ]*****
    public : void information (void)
      {
	cout << "<" << name << ">" << endl;

	for (uint32_t it = 0; it < nb_entity; it ++)
	  icache_dedicated [it]->information();
	for (uint32_t it = 0; it < nb_entity; it ++)
	  dcache_dedicated [it]->information();
	
	cache_shared->information();
      }
      

      friend ostream& operator<< (ostream& output_stream, const Cache & x)
      {
	output_stream << "<" << x.name << ">" << endl;
	  
	for (uint32_t it = 0; it < x.nb_entity; it ++)
	  output_stream << *x.icache_dedicated [it] << endl;
	for (uint32_t it = 0; it < x.nb_entity; it ++)
	  output_stream << *x.dcache_dedicated [it] << endl;
      
	output_stream << *x.cache_shared << endl;
      
	return output_stream;
      }

    };//end Cache
  };};
#endif //!CACHE_H
