#ifndef MEMORY_H
#define MEMORY_H

#include <list>
#include "systemc.h"
#include "Common/include/BitManipulation.h"
#include "Common/include/Debug.h"
#include "Common/include/Log2.h"
#include "Common/include/Test.h"
#include "Behavioural/include/Constants.h"
#include "Behavioural/include/Types.h"

//================================================================{Memory_t}
typedef struct 
{
  double                                  _cycle   ;
  uint32_t                                _context ;
  morpheo::behavioural::Tdcache_address_t _address ;
  morpheo::behavioural::Tdcache_type_t    _type    ;
  morpheo::behavioural::Tdcache_data_t    _data_old;
  morpheo::behavioural::Tdcache_data_t    _data_new;
} trace_memory_t;

class Memory_t
{
private : const uint32_t          _nb_context;
private : const uint32_t          _nb_word   ;
private : const uint32_t          _size_word ;
private : const uint32_t          _shift_addr;
private : const morpheo::behavioural::Tdcache_address_t _mask_addr ;
private :       morpheo::behavioural::Tdcache_data_t ** _data      ;
  
private : std::list<trace_memory_t> _trace_memory;

public  : Memory_t (uint32_t nb_context, 
		    uint32_t nb_word, 
		    uint32_t size_word):
  _nb_context   (nb_context),
  _nb_word      (nb_word   ),
  _size_word    (size_word ),
  _shift_addr   (morpheo::log2(size_word/8)),
  _mask_addr    (morpheo::gen_mask<morpheo::behavioural::Tdcache_address_t>(_shift_addr))
  {
    _data = new morpheo::behavioural::Tdcache_data_t * [nb_context];
    
    for (uint32_t i=0; i<nb_context; i++)
      {
	_data [i] = new morpheo::behavioural::Tdcache_data_t [nb_word];
	
	// random data
	for (uint32_t j=0; j<nb_word; j++)
	  _data [i][j] = static_cast<morpheo::behavioural::Tdcache_data_t>(rand());
      }

    std::cout << "=====[ Memory's information ]" << std::endl
	      << "  * _nb_context : " << _nb_context << std::endl
	      << "  * _nb_word    : " << _nb_word    << std::endl
	      << "  * _size_word  : " << _size_word  << std::endl
	      << "  * _shift_addr : " << _shift_addr << std::endl
	      << "  * _mask_addr  : " << std::hex << _mask_addr << std::dec << std::endl;
  }

public  : ~Memory_t (void)
  {
    delete [] _data;
  }

public  : morpheo::behavioural::Tdcache_data_t access (uint32_t          context, 
						       morpheo::behavioural::Tdcache_address_t address,
						       morpheo::behavioural::Tdcache_type_t    type,
						       morpheo::behavioural::Tdcache_data_t    data)
  {
    std::cout << "<Memory::access>" << std::endl
	      << " * context : " << context << std::endl
	      << " * address : " << std::hex << address << std::dec << std::endl
	      << " * type    : " << type << std::endl
	      << " * wdata   : " << std::hex << data << std::dec << std::endl;

    morpheo::behavioural::Tdcache_data_t rdata;

    if ((type == DCACHE_TYPE_LOAD_8 ) or
	(type == DCACHE_TYPE_LOAD_16) or
	(type == DCACHE_TYPE_LOAD_32) or
	(type == DCACHE_TYPE_LOAD_64) )
      rdata = read  (context, address, type);
    else
      if ((type == DCACHE_TYPE_STORE_8 ) or
	  (type == DCACHE_TYPE_STORE_16) or
	  (type == DCACHE_TYPE_STORE_32) or
	  (type == DCACHE_TYPE_STORE_64) )
	rdata = write (context, address, type, data);
      else
	rdata = other (context, address, type);

    std::cout << " * rdata   : " << std::hex << rdata << std::dec << std::endl;

    return rdata;
  }

public  : morpheo::behavioural::Tdcache_data_t read_lsq (uint32_t          context,
							 morpheo::behavioural::Tdcache_address_t address,
							 morpheo::behavioural::Tdcache_type_t    type)
  {
    if (context>_nb_context)
      TEST_KO("<Memory_t::read> nb context is too high");

    morpheo::behavioural::Tdcache_address_t LSB = address &  _mask_addr;
    morpheo::behavioural::Tdcache_address_t MSB = address >> _shift_addr;

    if (MSB >_nb_word)
      TEST_KO("<Memory_t::read> address is too high : %8.x", address);
    
    morpheo::behavioural::Tdcache_data_t data = _data [context][MSB] >> (LSB<<3);

    switch (type)
      {
      case OPERATION_MEMORY_LOAD_8_Z  : 
      case OPERATION_MEMORY_LOAD_16_Z : 
      case OPERATION_MEMORY_LOAD_32_Z : 
      case OPERATION_MEMORY_LOAD_64_Z : 
      case OPERATION_MEMORY_LOAD_8_S  : 
      case OPERATION_MEMORY_LOAD_16_S : 
      case OPERATION_MEMORY_LOAD_32_S : 
      case OPERATION_MEMORY_LOAD_64_S : return morpheo::extend<morpheo::behavioural::Tdcache_data_t>(_size_word,data, is_operation_memory_load_signed(type),memory_size(type));
      default : TEST_KO("<Memory_t::read_lsq> invalide type"); return data;
      }
  }

private : morpheo::behavioural::Tdcache_data_t read (uint32_t context,
						     morpheo::behavioural::Tdcache_address_t address,
						     morpheo::behavioural::Tdcache_type_t type)
  {
    // Address's Read must be aligned

//     if ((address & _mask_addr) != 0)
//       TEST_KO("<Memory_t::read> Address is not aligned");

    if (context>_nb_context)
      TEST_KO("<Memory_t::read> nb context is too high");

    morpheo::behavioural::Tdcache_address_t MSB = address >> _shift_addr;

    if (MSB >_nb_word)
      TEST_KO("<Memory_t::read> address is too high");

    morpheo::behavioural::Tdcache_data_t rdata = _data [context][MSB];
    trace_memory_t trace;

    trace._cycle    = simulation_cycle();
    trace._context  = context;
    trace._address  = address;
    trace._type     = type;
    trace._data_old = rdata;
    trace._data_new = rdata;

    _trace_memory.push_back(trace);
    
    return rdata;
  }

private : morpheo::behavioural::Tdcache_data_t write (uint32_t context, 
						      morpheo::behavioural::Tdcache_address_t address,
						      morpheo::behavioural::Tdcache_type_t type,
						      morpheo::behavioural::Tdcache_data_t data)
  {
    std::cout << "   * write" << std::endl;

    if (context>_nb_context)
      TEST_KO("<Memory_t::read> nb context is too high");

    if (address>_nb_word)
      TEST_KO("<Memory_t::read> address is too high");

    morpheo::behavioural::Tdcache_address_t LSB = address &  _mask_addr;
    morpheo::behavioural::Tdcache_address_t MSB = address >> _shift_addr;

    std::cout << std::hex
	 << "     * LSB         : " << LSB << std::endl
	 << "     * MSB         : " << MSB << std::endl
	 << std::dec;
  
    morpheo::behavioural::Tdcache_data_t data_old    = _data [context][MSB];

    // exemple to size_word = 32b
    // LSB index_min
    // 0   0
    // 1   8
    // 2   16
    // 3   24

    uint32_t memory_size = ((type==DCACHE_TYPE_STORE_16)?MEMORY_SIZE_16:
			    ((type==DCACHE_TYPE_STORE_32)?MEMORY_SIZE_32:
			     ((type==DCACHE_TYPE_STORE_64 )?MEMORY_SIZE_64:MEMORY_SIZE_8)));

    uint32_t index_min = LSB<<3; // *8
    uint32_t index_max = index_min+memory_size;

    std::cout << "     * type        : " << type << std::endl
	 << "     * memory_size : " << memory_size << std::endl
	 << "     * index_min   : " << index_min << std::endl
	 << "     * index_max   : " << index_max << std::endl;
    
    morpheo::behavioural::Tdcache_data_t data_insert = data<<index_min; // the data is aligned at LSB

//     std::cout << "read :" << std::endl
// 	 << " * context     : " << context << std::endl
// 	 << std::hex		    
// 	 << " * address     : " << address << std::endl
// 	 << "   * LSB       : " << LSB       << std::endl
// 	 << "   * MSB       : " << MSB       << std::endl
// 	 << std::dec
// 	 << "   * index_min : " << index_min << std::endl
// 	 << "   * index_max : " << index_max << std::endl
// 	 << " * type        : " << type    << std::endl;

    if (index_max > _size_word)
      TEST_KO("<Memory_t::read> illegal value of index_max : %d, size_word is %d.",index_max,_size_word);

    morpheo::behavioural::Tdcache_data_t data_new = morpheo::insert<morpheo::behavioural::Tdcache_data_t>(data_old, data_insert, index_max-1, index_min);

    _data [context][MSB] = data_new;
    
    std::cout << std::hex
	      << "     * data_old    : " << data_old << std::endl
	      << "     * data_new    : " << data_new << std::endl
	      << std::dec;


    trace_memory_t trace;

    trace._cycle    = simulation_cycle();
    trace._context  = context;
    trace._address  = address;
    trace._type     = type;
    trace._data_old = data_old;
    trace._data_new = data_new;

    _trace_memory.push_back(trace);
  
    return data_old;
  }

private : morpheo::behavioural::Tdcache_data_t other (uint32_t context,
						      morpheo::behavioural::Tdcache_address_t address,
						      morpheo::behavioural::Tdcache_type_t type)
  {
    trace_memory_t trace;

    trace._cycle    = simulation_cycle();
    trace._context  = context;
    trace._address  = address;
    trace._type     = type;
    trace._data_old = 0;
    trace._data_new = 0;

    _trace_memory.push_back(trace);

    return 0;
  }

public : void trace (void)
  {
    for (std::list<trace_memory_t>::iterator i=_trace_memory.begin(); i!= _trace_memory.end(); i++)
      {
	std::cout << "{" << i->_cycle << "}\t"
		  << i->_context << " - ";

	switch(i->_type)
	  {
	  case DCACHE_TYPE_LOCK            : std::cout << "DCACHE_TYPE_LOCK           "; break;
	  case DCACHE_TYPE_INVALIDATE      : std::cout << "DCACHE_TYPE_INVALIDATE     "; break;
	  case DCACHE_TYPE_PREFETCH        : std::cout << "DCACHE_TYPE_PREFETCH       "; break;
	  case DCACHE_TYPE_FLUSH           : std::cout << "DCACHE_TYPE_FLUSH          "; break;
	  case DCACHE_TYPE_SYNCHRONIZATION : std::cout << "DCACHE_TYPE_SYNCHRONIZATION"; break;
	  case DCACHE_TYPE_LOAD_8          : std::cout << "DCACHE_TYPE_LOAD_8         "; break;
	  case DCACHE_TYPE_LOAD_16         : std::cout << "DCACHE_TYPE_LOAD_16        "; break;
	  case DCACHE_TYPE_LOAD_32         : std::cout << "DCACHE_TYPE_LOAD_32        "; break;
	  case DCACHE_TYPE_LOAD_64         : std::cout << "DCACHE_TYPE_LOAD_64        "; break;
	  case DCACHE_TYPE_STORE_8         : std::cout << "DCACHE_TYPE_STORE_8        "; break;
	  case DCACHE_TYPE_STORE_16        : std::cout << "DCACHE_TYPE_STORE_16       "; break;
	  case DCACHE_TYPE_STORE_32        : std::cout << "DCACHE_TYPE_STORE_32       "; break;
	  case DCACHE_TYPE_STORE_64        : std::cout << "DCACHE_TYPE_STORE_64       "; break;
	  }
	std::cout << " - "
		  << std::hex
		  << i->_address << " : "
		  << i->_data_old << " -> "
		  << i->_data_new << std::endl
		  << std::dec;
      }
  }
};

inline void test_Memory_t (void)
{
  const uint32_t _nb_context =   4;
  const uint32_t _size_word  =  32;
  const uint32_t _nb_word    = 100; 
  
  Memory_t * memory = new Memory_t (_nb_context, _nb_word, _size_word);
  
  memory -> access (2, 0x10, DCACHE_TYPE_STORE_32, 0xdeadbeef);
  memory -> access (2, 0x14, DCACHE_TYPE_STORE_16, 0xdada5678);
  memory -> access (2, 0x16, DCACHE_TYPE_STORE_16, 0xbead1234);
  memory -> access (2, 0x18, DCACHE_TYPE_STORE_8 , 0x45675681);
  memory -> access (2, 0x19, DCACHE_TYPE_STORE_8 , 0x1f311219);
  memory -> access (2, 0x1a, DCACHE_TYPE_STORE_8 , 0x2e075607);
  memory -> access (2, 0x1b, DCACHE_TYPE_STORE_8 , 0x19811221);
  
  TEST(morpheo::behavioural::Tdcache_data_t, memory -> access (2, 0x10, DCACHE_TYPE_LOAD_32, 0), 0xdeadbeef);
  TEST(morpheo::behavioural::Tdcache_data_t, memory -> access (2, 0x14, DCACHE_TYPE_LOAD_32, 0), 0x12345678);
  TEST(morpheo::behavioural::Tdcache_data_t, memory -> access (2, 0x18, DCACHE_TYPE_LOAD_32, 0), 0x21071981);

  TEST(morpheo::behavioural::Tdcache_data_t, memory -> access (2, 0x10, DCACHE_TYPE_LOAD_32, 0), 0xdeadbeef);
  TEST(morpheo::behavioural::Tdcache_data_t, memory -> access (2, 0x10, DCACHE_TYPE_LOAD_16, 0), 0xbeef);
  TEST(morpheo::behavioural::Tdcache_data_t, memory -> access (2, 0x12, DCACHE_TYPE_LOAD_16, 0), 0xdead);
  TEST(morpheo::behavioural::Tdcache_data_t, memory -> access (2, 0x10, DCACHE_TYPE_LOAD_8 , 0), 0xef);
  TEST(morpheo::behavioural::Tdcache_data_t, memory -> access (2, 0x11, DCACHE_TYPE_LOAD_8 , 0), 0xbe);
  TEST(morpheo::behavioural::Tdcache_data_t, memory -> access (2, 0x12, DCACHE_TYPE_LOAD_8 , 0), 0xad);
  TEST(morpheo::behavioural::Tdcache_data_t, memory -> access (2, 0x13, DCACHE_TYPE_LOAD_8 , 0), 0xde);

  TEST(morpheo::behavioural::Tdcache_data_t, memory -> read_lsq (2, 0x10, OPERATION_MEMORY_LOAD_8_Z ), 0x000000ef);
  TEST(morpheo::behavioural::Tdcache_data_t, memory -> read_lsq (2, 0x10, OPERATION_MEMORY_LOAD_8_S ), 0xffffffef);
  TEST(morpheo::behavioural::Tdcache_data_t, memory -> read_lsq (2, 0x10, OPERATION_MEMORY_LOAD_16_Z), 0x0000beef);
  TEST(morpheo::behavioural::Tdcache_data_t, memory -> read_lsq (2, 0x10, OPERATION_MEMORY_LOAD_16_S), 0xffffbeef);
  TEST(morpheo::behavioural::Tdcache_data_t, memory -> read_lsq (2, 0x10, OPERATION_MEMORY_LOAD_32_Z), 0xdeadbeef);
  TEST(morpheo::behavioural::Tdcache_data_t, memory -> read_lsq (2, 0x10, OPERATION_MEMORY_LOAD_32_S), 0xdeadbeef);

  TEST(morpheo::behavioural::Tdcache_data_t, memory -> read_lsq (2, 0x12, OPERATION_MEMORY_LOAD_8_Z ), 0x000000ad);
  TEST(morpheo::behavioural::Tdcache_data_t, memory -> read_lsq (2, 0x12, OPERATION_MEMORY_LOAD_8_S ), 0xffffffad);
  TEST(morpheo::behavioural::Tdcache_data_t, memory -> read_lsq (2, 0x12, OPERATION_MEMORY_LOAD_16_Z), 0x0000dead);
  TEST(morpheo::behavioural::Tdcache_data_t, memory -> read_lsq (2, 0x12, OPERATION_MEMORY_LOAD_16_S), 0xffffdead);

  TEST(morpheo::behavioural::Tdcache_data_t, memory -> read_lsq (2, 0x14, OPERATION_MEMORY_LOAD_8_Z ), 0x00000078);
  TEST(morpheo::behavioural::Tdcache_data_t, memory -> read_lsq (2, 0x14, OPERATION_MEMORY_LOAD_8_S ), 0x00000078);
  TEST(morpheo::behavioural::Tdcache_data_t, memory -> read_lsq (2, 0x18, OPERATION_MEMORY_LOAD_16_Z), 0x00001981);
  TEST(morpheo::behavioural::Tdcache_data_t, memory -> read_lsq (2, 0x18, OPERATION_MEMORY_LOAD_16_S), 0x00001981);
  TEST(morpheo::behavioural::Tdcache_data_t, memory -> read_lsq (2, 0x18, OPERATION_MEMORY_LOAD_32_Z), 0x21071981);
  TEST(morpheo::behavioural::Tdcache_data_t, memory -> read_lsq (2, 0x18, OPERATION_MEMORY_LOAD_32_S), 0x21071981);

  TEST(morpheo::behavioural::Tdcache_data_t, memory -> read_lsq (2, 0x1a, OPERATION_MEMORY_LOAD_8_Z ), 0x00000007);
  TEST(morpheo::behavioural::Tdcache_data_t, memory -> read_lsq (2, 0x1a, OPERATION_MEMORY_LOAD_8_S ), 0x00000007);
  TEST(morpheo::behavioural::Tdcache_data_t, memory -> read_lsq (2, 0x1a, OPERATION_MEMORY_LOAD_16_Z), 0x00002107);
  TEST(morpheo::behavioural::Tdcache_data_t, memory -> read_lsq (2, 0x1a, OPERATION_MEMORY_LOAD_16_S), 0x00002107);

  delete memory;
}

#endif
