#ifndef CACHE_ONELEVEL_H
#define CACHE_ONELEVEL_H

#include <stdint.h>
#include <math.h>
#include <iostream>
#include <iomanip>
#include "../file/sort_file_dynamic.h"
#include "type_req_cache.h"
#include "tag.h"
#include "address.h"
#include "access_port.h"
#include "write_buffer.h"

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

namespace hierarchy_memory          {
namespace cache                     {
namespace cache_multilevel          {
namespace cache_onelevel            {

  class param_cache_t
  {
  public : const char *   name         ;
  public : uint32_t nb_line      ;
  public : uint32_t size_line    ;
  public : uint32_t size_word    ;
  public : uint32_t associativity;
  public : uint32_t hit_latence  ;
  public : uint32_t miss_penality;
    
  public : param_cache_t () {};

  public : param_cache_t (const char   * name         ,
			  uint32_t nb_line      ,
			  uint32_t size_line    ,
			  uint32_t size_word    ,
			  uint32_t associativity,
			  uint32_t hit_latence  ,
			  uint32_t miss_penality
			  ) :
    name (name)
    {
      //this->name          = name         ;
      this->nb_line       = nb_line      ;
      this->size_line     = size_line    ;
      this->size_word     = size_word    ;
      this->associativity = associativity;
      this->hit_latence   = hit_latence  ;
      this->miss_penality = miss_penality;
    }

    friend ostream& operator<< (ostream& output_stream, const param_cache_t & x)
    {
      output_stream << "<" << x.name   << "> "
		    << x.nb_line       << " "
		    << x.size_line     << " "
		    << x.size_word     << " "
		    << x.associativity << " "
		    << x.hit_latence   << " "
		    << x.miss_penality;
    
      return output_stream;
    }
  };//end param_cache_t
 
  class param_t
  {
  public : uint32_t      nb_port    ;
  public : param_cache_t param_cache;
    
  public : param_t (uint32_t      nb_port    ,
		    param_cache_t param_cache)
    {
      this->nb_port     = nb_port      ;
      this->param_cache = param_cache  ;
    }

    friend ostream& operator<< (ostream& output_stream, const param_t & x)
    {
      output_stream << x.nb_port << " " 
		    << x.param_cache;
      return output_stream;
    }

  };//end param_t

  class Cache_OneLevel
  {
  protected : const uint32_t  nb_port       ;
  protected : const uint32_t  nb_line       ;
  protected : const uint32_t  size_line     ;
  protected : const uint32_t  size_word     ;
  protected : const uint32_t  associativity ;
  protected : const uint32_t  hit_latence   ;
  protected : const uint32_t  miss_penality ;
    
  private   : char          *  name;
  private   : tag_t         ** tag;
  private   : access_port_t *  access_port;
  private   : address_t        size_address;
  private   : Sort_File_Dynamic<write_buffer_t> * write_buffer;

    //*****[ constructor ]*****
  public : Cache_OneLevel (param_t param):
      nb_port       (param.nb_port                  ),
      nb_line       (param.param_cache.nb_line      ),
      size_line     (param.param_cache.size_line    ),
      size_word     (param.param_cache.size_word    ),
      associativity (param.param_cache.associativity),
      hit_latence   (param.param_cache.hit_latence  ),
      miss_penality (param.param_cache.miss_penality)
      {
	uint32_t size_name = strlen(param.param_cache.name)+1;
	name               = new char [size_name];
	strncpy(name,param.param_cache.name,size_name);

	if ((nb_line * size_line * associativity) == 0)
	  {
	    cerr << "<" << name << ".{Cache_OneLevel}> all parameter must be greater that 0" << endl;
	    cerr << " nb_line       : " << nb_line       << endl;
	    cerr << " size_line     : " << size_line     << endl;
	    cerr << " associativity : " << associativity << endl;
	    exit(1);
	  }
	if ((nb_line % associativity) != 0)
	  {
	    cerr << "<" << name << ".{Cache_OneLevel}> nb_line must be a mutiple of associativity" << endl;
	    cerr << "  * nb_line       : " << nb_line       << endl;
	    cerr << "  * associativity : " << associativity << endl;
	    exit(1);
	  }

	if ((double)nb_line    != ::pow(2,::log2(nb_line)))
	  {
	    cerr << "<" << name << ".{Cache_OneLevel}> nb_line must be a mutiple of 2^n" << endl;
	    cerr << "  * nb_line       : " << nb_line       << endl;
	    exit(1);
	  }

	if ((double)size_line != ::pow(2,::log2(size_line)))
	  {
	    cerr << "<" << name << ".{Cache_OneLevel}> size_line must be a mutiple of 2^n" << endl;
	    cerr << "  * size_line     : " << size_line     << endl;
	    exit(1);
	  }

	if ((double)size_word != ::pow(2,::log2(size_word)))
	  {
	    cerr << "<" << name << ".{Cache_OneLevel}> size_word must be a mutiple of 2^n" << endl;
	    cerr << "  * size_word     : " << size_word     << endl;
	    exit(1);
	  }
	
	write_buffer= new Sort_File_Dynamic<write_buffer_t> (sort_file::param_t("write_buffer",5));
	access_port = new access_port_t [nb_port];
	tag         = new tag_t *       [nb_line/associativity];
	for (uint32_t it = 0; it < nb_line/associativity; it ++)
	  tag [it] = new tag_t [associativity];

	size_address.offset  = (uint32_t) log2(size_line * size_word);
	size_address.familly = (uint32_t) log2(nb_line/associativity);
	size_address.tag     = 32 - size_address.familly - size_address.offset;
     }
      
      //*****[ destructor ]*****
    public : ~Cache_OneLevel ()
      {
// 	delete tag;
// 	delete access_port;
      };

      //*****[ reset ]*****
    public : void reset ()
      {
	for (uint32_t x = 0; x < nb_port; x ++)
	  access_port[x].valid = false;

	for (uint32_t x = 0; x < nb_line/associativity; x ++)
	  for (uint32_t y = 0; y < associativity; y ++)
	    {
	      tag[x][y].valid      = false;
	      tag[x][y].index_lru  = y;
	    }
	write_buffer->reset();
      }

      //*****[ transition ]*****
    public : void transition ()
    {
	// scan all port - test if have a transaction
	for (int32_t x = nb_port-1; x >= 0; x --)
	  {
	    if (access_port[x].valid == true)
	      {
		access_port[x].valid = false;
		
		// Update LRU
		//  * hit  : now
		//  * miss : after the return of next cache
		
		if (access_port[x].hit == HIT_CACHE)
		  {// Hit
		    update_lru(access_port[x].address.familly,access_port[x].num_associativity);
		  }

		if (access_port[x].hit == MISS)
		  {// Miss
		    // Write in write_buffer
		    write_buffer->push(access_port[x].latence,write_buffer_t(access_port[x].address,access_port[x].trdid));
		  }
	      }
	  }
	
	// Test if a write_buffer have the result
	while ((write_buffer->empty() == false) && (write_buffer->read(0).delay == 0))
	  {
	    // Save in the cache
	    write_buffer_t val         = write_buffer->pop();
	    
	    uint32_t num_tag           = val.address.tag;
	    uint32_t num_familly       = val.address.familly;
	    uint32_t num_associativity = index_victim(num_familly);
	    
	    tag [num_familly][num_associativity].valid    = true;
	    tag [num_familly][num_associativity].address  = num_tag;
	    tag [num_familly][num_associativity].trdid    = val.trdid;
	    
	    update_lru(num_familly,num_associativity);
	  }

	write_buffer->transition();
      }
      //*****[ update_lru ]*****
    private : void update_lru (uint32_t familly, uint32_t num_associativity)
      {
	uint32_t current_lru = tag [familly][num_associativity].index_lru;
	
	for (uint32_t k = 0; k < associativity; k ++)
	  if (tag [familly][k].index_lru < current_lru)
	    tag [familly][k].index_lru ++;
	tag [familly][num_associativity].index_lru = 0;
      }
      
    //*****[ index_victim ]*****
    // return the index of the nex victim
  public : uint32_t index_victim (uint32_t familly)
    {
      uint32_t victim = 0;
      while (tag [familly][victim].index_lru != (associativity-1))
	{
	  victim ++;
	}

      return victim;
    }

      //*****[ translate_address ]*****
    private : address_t translate_address (uint32_t address)
      {
 	address_t address_translated;
	uint32_t  shift;

	address_translated.offset  = (address & ((uint32_t)-1 >> (32-(size_address.offset          ))));
	address                   -= address_translated.offset;
	shift                      = size_address.offset;
	address_translated.familly = (address & ((uint32_t)-1 >> (32-(size_address.familly + shift))))>>shift;
	address                   -= address_translated.familly;
	shift                     += size_address.familly;
	address_translated.tag     = (address & ((uint32_t)-1 >> (32-(size_address.tag     + shift))))>>shift;

	return address_translated;
      }

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

    //*****[ access ]*****
    // Return hit (true) 
      // uncache is to stocke the address on the cache
  public : type_rsp_cache_t access (uint32_t nb_port, uint32_t address, uint32_t trdid, type_req_cache_t type, direction_req_cache_t dir)
    {
      switch (type)
	{
	case UNCACHED   : return access_uncached   (nb_port,address,trdid    ); break;
	case INVALIDATE : return access_invalidate (nb_port,address,trdid    ); break;
	case FLUSH      : return access_flush      (nb_port,address,trdid    ); break;
	case PREFETCH   : // no difference with the simple read cached (have no add a prefetch cache)
	case CACHED     : return access_cached     (nb_port,address,trdid,dir); break;
	default         : cout << "<Cache_Onelevel.access> unkonow type : " << endl; exit(1); break;
	}
    }

    // *****[ access_cached ]*****
  public : type_rsp_cache_t access_cached (uint32_t nb_port, uint32_t address, uint32_t trdid, direction_req_cache_t dir)
    {
	address_t        address_translate  = translate_address(address);
	uint32_t         num_associativity  = hit_cache        (trdid, address_translate);
	uint32_t         num_port           = hit_access_port  (trdid, address_translate);
	uint32_t         num_write_buffer   = hit_write_buffer (trdid, address_translate);
	      	
	if (num_port == this->nb_port)
	  num_port = nb_port;

	uint32_t         latence            ;
	type_rsp_cache_t res                = MISS;

	bool             is_in_cache        = (num_associativity != associativity);
	bool             is_in_access_port  = (num_port != nb_port);
	bool             is_in_write_buffer = false;

	if (is_in_access_port == true)
	  {
	    res     = HIT_BYPASS;
	    latence = access_port[num_port].latence; //already compute
	  }
	else
	  if (is_in_cache == true)
	    {	
	      res     = HIT_CACHE;
	      latence = 0; // Hit !!!
	    }
	  else
	    {
	      // Search in the write buffer, and test if have a miss
	      if ( num_write_buffer == write_buffer->nb_slot_use())
		  {
		    res     = MISS;
		    latence = miss_penality; // miss -> access at down of cache,  + respons at the up of cache
		  }
	      else
		{
		  res                = HIT_WRITE_BUFFER;
		  is_in_write_buffer = true;
		  latence            = write_buffer->read(num_write_buffer).delay;
		}
	    }

	// access_port valid = there are a new request to update
	//  -> no previous request in the same     cycle (hit in a access port)
	//  -> no previous request in the previous cycle (hit in the write buffer)
	
 	access_port[nb_port].valid             = ((is_in_access_port || is_in_write_buffer) == false);
	access_port[nb_port].address           = address_translate;
	access_port[nb_port].trdid             = trdid;
	access_port[nb_port].hit               = res;
	access_port[nb_port].num_associativity = num_associativity;
	access_port[nb_port].latence           = latence;

	return res;
    }

    // *****[ access_uncached ]*****
  public : type_rsp_cache_t access_uncached (uint32_t nb_port, uint32_t address, uint32_t trdid)
    {
      access_port[nb_port].valid             = false;
      access_port[nb_port].trdid             = trdid;
      access_port[nb_port].hit               = MISS;
      access_port[nb_port].latence           = miss_penality;
      
      return MISS;
    }
    // *****[ access_invalidate ]*****
  public : type_rsp_cache_t access_invalidate (uint32_t nb_port, uint32_t address, uint32_t trdid)
    {
      cerr << "<" << name << ".{Cache_OneLevel}.access_invalidate> Not implemented" << endl;
      exit (0);
      return MISS;
    }

    // *****[ access_flush ]*****
  public : type_rsp_cache_t access_flush (uint32_t nb_port, uint32_t address, uint32_t trdid)
    {
      cerr << "<" << name << ".{Cache_OneLevel}.access_flush> Not implemented" << endl;
      exit (0);
      return MISS;
    }

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

    //*****[ hit_write_buffer ]*****

    // If a instruction is in the write_buffer, return the position
    // else, return the number of elt in the write_buffer
  public : uint32_t hit_write_buffer (uint32_t trdid, address_t address)
    {
	uint32_t num_write_buffer = 0;
	// Scan the write_buffer
	for (num_write_buffer = 0; num_write_buffer < write_buffer->nb_slot_use(); num_write_buffer ++)
	  {
	    slot_t<write_buffer_t> val = write_buffer->read(num_write_buffer);

	    if ( (val.data.trdid           == trdid          ) &&
		 (val.data.address.tag     == address.tag    ) &&
		 (val.data.address.familly == address.familly))
	      break;
	  }
	
	return num_write_buffer;
    }

    //*****[ hit_access_port ]*****
    // return the number of associativity if hit
    // return nb_accosiativity if miss
  public : uint32_t hit_cache (uint32_t trdid, address_t address)
      {
	uint32_t num_associativity;
	for (num_associativity = 0; num_associativity < associativity; num_associativity ++)
	  // Hit if :
	  //  * in the line
	  //  * in a way associative
	  //  -> there are the same tag 
	  //               the same trdid
	  //               is valid
	  if ( (tag [address.familly][num_associativity].address == address.tag) && 
	       (tag [address.familly][num_associativity].valid   == true       ) &&
	       (tag [address.familly][num_associativity].trdid   == trdid      ) )
	      break;

	return num_associativity;
      }

    //*****[ hit_access_port ]*****
    // Return the number of port if hit
    // Return nb_port if miss
  public : uint32_t hit_access_port (uint32_t trdid, address_t address)
      {
	uint32_t it_num_port = 0;

	// scan all port - test if have a transaction
	for (it_num_port = 0; it_num_port < nb_port; it_num_port ++)
	  if ( (access_port[it_num_port].valid           == true           ) &&
	       (access_port[it_num_port].trdid           == trdid          ) &&
	       (access_port[it_num_port].address.tag     == address.tag    ) &&
	       (access_port[it_num_port].address.familly == address.familly))
	    break;

	return it_num_port;
      }

      //*****[ need_slot ]*****
    public : uint32_t need_slot ()
      {
	uint32_t res = 0;
	// scan all port - test if have a transaction
	for (uint32_t x = 0; x < nb_port; x ++)
	  if (access_port[x].valid == true)
	    res ++;

	return res;
      }

      //*****[ latence ]*****
      // Return the time to have the data
      // uncache is to stocke the address on the cache
    public : uint32_t latence (uint32_t num_port)
      {
	uint32_t res = hit_latence + access_port[num_port].latence;

	return res;
      }

      //*****[ update_latence ]*****
      // Return the time to have the data
      // uncache is to stocke the address on the cache
  public : bool update_latence (uint32_t nb_port, uint32_t latence)
      {
	access_port[nb_port].latence = latence-hit_latence;
	return access_port[nb_port].valid;
      }

    //*****[ information ]*****
  public : void information (void)
    {
      cout << "<" << name << ">"                                                      << endl
	   << "  * Information"                                                       << endl
	   << "  * Capacity        : " << nb_line * size_line * size_word << " bytes" << endl
	   << "    * Nb line       : " << nb_line                                     << endl
	   << "    * Size line     : " << size_line                                   << endl
	   << "    * Size word     : " << size_word                                   << endl
	   << "    * Associativity : " << associativity                               << endl
	   << "  * Timing          : "                                                << endl
	   << "    * Hit  latence  : " << hit_latence                                 << endl
	   << "    * Miss penality : " << miss_penality                               << endl
	   << "    * Nb port       : " << nb_port                                     << endl
	   << endl;
    }
    
      friend ostream& operator<< (ostream& output_stream, const Cache_OneLevel & x)
      {
	output_stream << "<" << x.name << ">"                                                          << endl
		      << "  * Capacity        : " << x.nb_line * x.size_line * x.size_word << " bytes" << endl
		      << "    * Nb line       : " << x.nb_line                                         << endl
		      << "    * Size line     : " << x.size_line                                       << endl
		      << "    * Size word     : " << x.size_word                                       << endl
		      << "    * Associativity : " << x.associativity                                   << endl
	              << "  * Timing          : "                                                      << endl
		      << "    * Hit  latence  : " << x.hit_latence                                     << endl
		      << "    * Miss penality : " << x.miss_penality                                   << endl
	              << "    * Nb port       : " << x.nb_port                                         << endl
		      << endl;

	output_stream << "  * Tag"                                                                     << endl;
	for (uint32_t i = 0; i < x.nb_line / x.associativity; i ++)
	  {
	    for (uint32_t j = 0; j < x.associativity; j ++)
		output_stream << x.tag [i][j] << " | ";
	    output_stream << endl;
	  }
	
	output_stream << endl;
	output_stream << *x.write_buffer << endl;

	return output_stream;
      }

    };
};};};};
#endif //!CACHE_ONELEVEL_H

