#ifndef CACHE_MULTILEVEL_H
#define CACHE_MULTILEVEL_H

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

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

namespace hierarchy_memory          {
  namespace cache                     {
    namespace cache_multilevel          {

      class param_t
      {
      public : const char    * name         ;
      public : uint32_t        nb_level     ;
      public : uint32_t        nb_port      ;
      public : param_cache_t * param_cache  ;
  
      public : param_t () {};

      public : param_t (const char    * name        ,
			uint32_t        nb_level    ,
			uint32_t        nb_port     ,
			param_cache_t * param_cache ) :
	name (name)
	{
	  //this->name          = name         ;
	  this->nb_level      = nb_level     ;
	  this->nb_port       = nb_port      ;
	  this->param_cache   = param_cache  ;
	}
    
	friend ostream& operator<< (ostream& output_stream, const param_t & x)
	{
	  output_stream << "<" << x.name   << "> "
			<< x.nb_level << " " 
			<< x.nb_port;

	  for (uint32_t it = 0; it < x.nb_level; it ++)
	    output_stream << endl << x.param_cache [it];
	  return output_stream;
	}
    
      };//end param_t

      class access_t
      {
      public : uint32_t         num_port     ;
      public : type_rsp_cache_t hit          ; // result of access
      public : uint32_t         latence      ; // Latence of access
      public : uint32_t         last_nb_level; // if access is not a hit : last level of cache before a error
	
      public : access_t (uint32_t         num_port     ,
			 type_rsp_cache_t hit          ,
			 uint32_t         latence      ,
			 uint32_t         last_nb_level)
	{
	  this->num_port      = num_port     ;
	  this->hit           = hit          ;
	  this->latence       = latence      ;
	  this->last_nb_level = last_nb_level;
	}

	friend ostream& operator<< (ostream& output_stream, const access_t & x)
	{
	  output_stream << "["
			<< x.num_port << "] "  
			<< x.hit      << " " 
			<< x.latence  << " " 
			<< x.last_nb_level;
	  return output_stream;
	}
      };//access_t
  
      class Cache_Multilevel
      {
      private   : char            * name;
      protected : Cache_OneLevel ** hierarchy_cache;
      public    : const uint32_t    nb_level;
      public    : const uint32_t    nb_port ;

	//*****[ constructor ]*****
      public : Cache_Multilevel (param_t param) :
	nb_level (param.nb_level),
	nb_port  (param.nb_port )
	{
	  uint32_t size_name = strlen(param.name)+1;
	  name               = new char [size_name];
	  strncpy(name,param.name,size_name);

	  hierarchy_cache = new Cache_OneLevel * [nb_level];

	  // Creation of Cache hierarchy
	  for (uint32_t it = 0; it < nb_level; it ++)
	    hierarchy_cache [it] = new Cache_OneLevel (cache_onelevel::param_t(param.nb_port          ,
									       param.param_cache  [it]));
	}
      
	//*****[ destructor ]*****
      public : ~Cache_Multilevel ()
	{
	  for (uint32_t it = 0; it < nb_level; it ++)
	    delete hierarchy_cache [it];
	};
	//*****[ reset ]*****
      public : void reset ()
	{
	  for (uint32_t it = 0; it < nb_level; it ++)
	    hierarchy_cache [it]->reset();
	}

	//*****[ transition ]*****
      public : void transition ()
	{
	  for (uint32_t it = 0; it < nb_level; it ++)
	    hierarchy_cache [it]->transition();
	}

	//*****[ access ]*****
      public : access_t access (uint32_t num_port, uint32_t address, uint32_t trdid, type_req_cache_t type, direction_req_cache_t dir)
	{
	  uint32_t         time          = 0;
	  uint32_t         last_nb_level = (nb_level==0)?0:(nb_level-1);
	  type_rsp_cache_t hit           = MISS;

	  for (uint32_t it_nb_level = 0; it_nb_level < nb_level; it_nb_level ++)
	    {
	      hit = hierarchy_cache[it_nb_level]->access(num_port,address,trdid,type,dir);
	      time += hierarchy_cache [it_nb_level]->latence (num_port);

	      if ( (hit == HIT_CACHE)        || 
		   (hit == HIT_WRITE_BUFFER) || 
		   (hit == HIT_BYPASS)       )
		{
		  last_nb_level = it_nb_level;
		  break;
		}
	    }
	  return access_t (num_port,hit,time,last_nb_level);
	}

	//*****[ update_access ]*****
      public : uint32_t update_access (access_t cur_access)
	{
	  for (uint32_t it = 0; it < cur_access.last_nb_level; it ++)
	      hierarchy_cache [it]->update_latence (cur_access.num_port,cur_access.latence);

	  return cur_access.latence;
	}

	//*****[ 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
      public : uint32_t latence (uint32_t num_port, uint32_t address, uint32_t trdid, type_req_cache_t type, direction_req_cache_t dir)
	{
	  return update_access (access (num_port, address, trdid, type, dir));
	}

	//*****[ information ]*****
      public : void information (void)
	{
	  cout << "<" << name << ">" << endl;
	  for (uint32_t it = 0; it < nb_level; it ++)
	    hierarchy_cache [it]->information();
	}

	friend ostream& operator<< (ostream& output_stream, const Cache_Multilevel & x)
	{
	  output_stream << "<" << x.name << ">" << endl;
	  
	  for (uint32_t it = 0; it < x.nb_level; it ++)
	    output_stream << *x.hierarchy_cache [it] << endl;

	  return output_stream;
	}
      };
  
    };};};
#endif //!CACHE_MULTILEVEL_H

