/*
 * Copyright (c) 1999-2008 Mark D. Hill and David A. Wood
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met: redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer;
 * redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution;
 * neither the name of the copyright holders nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * Description: see RaceyPseudoThread.h
 */

#include "mem/ruby/tester/RaceyPseudoThread.hh"
#include "mem/ruby/tester/RaceyDriver.hh"
#include "gzstream.hh"

RaceyPseudoThread::RaceyPseudoThread(NodeID id, RaceyDriver& driver)
  : m_driver(driver), m_proc_id(id) {

  resetIC(); // IC contains the committed instruction number
  m_last_progress = 0;
  m_done = false;
  m_stop = 0;
  m_driver.eventQueue->scheduleEvent(this, 1);
}

RaceyPseudoThread::~RaceyPseudoThread() {
}

void RaceyPseudoThread::checkForDeadlock() {
  Time current_time = m_driver.eventQueue->getTime();
  if(!m_done && (current_time - m_last_progress) > g_DEADLOCK_THRESHOLD) {
    WARN_EXPR(m_proc_id);
    WARN_EXPR(m_ic_counter);
    WARN_EXPR(m_last_progress);
    ERROR_MSG("Deadlock detected.");
  }
}

void RaceyPseudoThread::performCallback(int proc, Address address, uint8_t * data ) {
  assert(proc == m_proc_id);

  DEBUG_EXPR(TESTER_COMP, LowPrio, proc);
  DEBUG_EXPR(TESTER_COMP, LowPrio, address);


  m_last_progress = m_driver.eventQueue->getTime();

  if(m_read) {
    int b0, b1, b2, b3;
    b0 = data[0]; b0 <<= 0;
    b1 = data[1]; b1 <<= 8;
    b2 = data[2]; b2 <<= 16;
    b3 = data[3]; b3 <<= 24;
    m_value = b0 | b1 | b2 | b3;
  } else {
    char b0, b1, b2, b3;
    b0 = (m_value>>0)&0xFF; data[0] = b0;
    b1 = (m_value>>8)&0xFF; data[1] = b1;
    b2 = (m_value>>16)&0xFF; data[2] = b2;
    b3 = (m_value>>24)&0xFF; data[3] = b3;
  }

  // schedule wakeup for next requests in next cycle
  m_driver.eventQueue->scheduleEvent(this, 1);

  // new instruction
  m_ic_counter++;

}

void RaceyPseudoThread::wakeup() {
  // for debug
  if(m_stop != 0) {
    cout << m_proc_id << " " << m_stop << ((m_read)?  " read ":" written ") << m_value;
    if(0) cout << " [" << m_driver.eventQueue->getTime() << "]";
    cout << endl;
  }

  assert(!m_done);

  // Note, this function can not have ANY local variable!

  switch(m_stop) {
  case 0:
    break;
  case 1:
    goto L1;
  case 2:
    goto L2;
  case 3:
    goto L3;
  case 4:
    goto L4;
  case 5:
    goto L5;
  case 6:
    goto L6;
  case 7:
    goto L7;
  case 8:
    goto L8;
  case 9:
    goto L9;
  case 10:
    goto L10;
  default:
    WARN_EXPR(m_stop);
    ERROR_MSG("RaceyPseudoThread: Bad context point!");
  }

  //
  // initialization
  //
  if(m_proc_id == 0) {
    for(m_looper = 0; m_looper < m_driver.m_num_procs; m_looper++) {
      store_sig(m_looper, m_looper+1);
      m_stop = 6; return;
L6:   {};
    }
    for(m_looper = 0; m_looper < M_ELEM; m_looper++) {
      store_m(m_looper, M_ELEM-m_looper);
      m_stop = 7; return;
L7:   {};
    }

    // init done
    m_initialized = true;
  } else {
    // other processors
    if(!m_driver.Thread0Initialized()) {
      // wait for processors 0
      m_driver.eventQueue->scheduleEvent(this, 1);
      return;
    }
  }

  cout << "Thread " << m_proc_id << " started in parallel phase" << endl;

  //
  // main thread body
  //
  for(m_looper = 0 ; m_looper < m_driver.m_tester_length; m_looper++) {
    /* m_value = */ load_sig(m_proc_id);
    m_stop = 1; return;
L1: {};
    m_num = m_value;
    m_index1 = m_num%M_ELEM;
    /* m_value = */ load_m(m_index1);
    m_stop = 2; return;
L2: {};
    m_num = mix(m_num, m_value);
    m_index2 = m_num%M_ELEM;
    /* m_value = */ load_m(m_index2);
    m_stop = 3; return;
L3: {};
    m_num = mix(m_num, m_value);
    store_m(m_index2, m_num);
    m_stop = 4; return;
L4: {};
    store_sig(m_proc_id, m_num);
    m_stop = 5; return;
L5: {};
  } // end for

  //
  // compute final sig
  //
  if(m_proc_id == 0) {
    // wait for other threads
    while (m_driver.runningThreads() > 1) {
      m_driver.registerThread0Wakeup();
      m_stop = 10; return;
L10: {};
    }

    /* m_value = */ load_sig(0);
    m_stop = 8; return;
L8: {};
    m_final_sig = m_value;
    for(m_looper = 1; m_looper < m_driver.m_num_procs; m_looper++) {
      /* m_value = */ load_sig(m_looper);
      m_stop = 9; return;
L9:   {};
      m_final_sig = mix(m_value, m_final_sig);
    }
  } // processors 0

  // done
  m_driver.joinThread();
  m_done = true;
}

void RaceyPseudoThread::load_sig(unsigned index) {
  cout << m_proc_id << " : load_sig " << index << endl;

  m_read = true;
  // timestamp, threadid, action, and logical address are used only by transactional memory, should be augmented
  uint8_t * read_data = new uint8_t[4];

  char name [] = "Sequencer_";
  char port_name [13];
  sprintf(port_name, "%s%d", name, m_proc_id);

  // pc is zero, problem?
  int64_t request_id = libruby_issue_request(libruby_get_port_by_name(port_name), RubyRequest(sig(index), read_data, 4, 0, RubyRequestType_LD, RubyAccessMode_User));
  
  ASSERT(m_driver.requests.find(request_id) == m_driver.requests.end());

  struct address_data request_data;
  request_data.address = Address(sig(index));
  request_data.data = read_data;
  m_driver.requests.insert(make_pair(request_id, make_pair(m_proc_id, request_data)));
  
  /*sequencer()->makeRequest(CacheMsg(Address(sig(index)), Address(sig(index)), CacheRequestType_LD,
                                    Address(physical_address_t(1)),
                                    AccessModeType_UserMode, 4,
                                    PrefetchBit_No, 0, Address(0), 
                                    0, 0 , false)); */
}

void RaceyPseudoThread::load_m(unsigned index) {
  // cout << m_proc_id << " : load_m " << index << endl;

  m_read = true;
  uint8_t * read_data = new uint8_t[4];

  char name [] = "Sequencer_";
  char port_name [13];
  sprintf(port_name, "%s%d", name, m_proc_id);

  // pc is zero, problem?
  int64_t request_id = libruby_issue_request(libruby_get_port_by_name(port_name), RubyRequest(m(index), read_data, 4, 0, RubyRequestType_LD, RubyAccessMode_User));
  
  ASSERT(m_driver.requests.find(request_id) == m_driver.requests.end());

  struct address_data request_data;
  request_data.address = Address(m(index));
  request_data.data = read_data;
  m_driver.requests.insert(make_pair(request_id, make_pair(m_proc_id, request_data)));
  
  /*sequencer()->makeRequest(CacheMsg(Address(m(index)), Address(m(index)), CacheRequestType_LD,
                                    Address(physical_address_t(1)),
                                    AccessModeType_UserMode, 4,
                                    PrefetchBit_No, 0, Address(0), 
                                    0, 0, false)); */
}

void RaceyPseudoThread::store_sig(unsigned index, unsigned value) {
  cout << m_proc_id << " : store_sig " << index << " " << value << endl;

  m_read = false;
  m_value = value;
  uint8_t * write_data = new uint8_t[4];


  memcpy(write_data, &value, 4);

  char name [] = "Sequencer_";
  char port_name [13];
  sprintf(port_name, "%s%d", name, m_proc_id);

  // pc is zero, problem?
  int64_t request_id = libruby_issue_request(libruby_get_port_by_name(port_name), RubyRequest(sig(index), write_data, 4, 0, RubyRequestType_ST, RubyAccessMode_User));
  
  ASSERT(m_driver.requests.find(request_id) == m_driver.requests.end());

  struct address_data request_data;
  request_data.address = Address(sig(index));
  request_data.data = write_data;
  m_driver.requests.insert(make_pair(request_id, make_pair(m_proc_id, request_data)));  

  /*sequencer()->makeRequest(CacheMsg(Address(sig(index)), Address(sig(index)), CacheRequestType_ST,
                                    Address(physical_address_t(1)),
                                    AccessModeType_UserMode, 4,
                                    PrefetchBit_No, 0, Address(0),
                                    0, 0, false)); */
}

void RaceyPseudoThread::store_m(unsigned index, unsigned value) {
  //cout << m_proc_id << " : store_m " << index << endl;

  m_read = false;
  m_value = value;
  uint8_t * write_data = new uint8_t[4];
  memcpy(write_data, &value, 4);

  char name [] = "Sequencer_";
  char port_name [13];
  sprintf(port_name, "%s%d", name, m_proc_id);

  // pc is zero, problem?
  int64_t request_id = libruby_issue_request(libruby_get_port_by_name(port_name), RubyRequest(m(index), write_data, 4, 0, RubyRequestType_ST, RubyAccessMode_User));
  
  ASSERT(m_driver.requests.find(request_id) == m_driver.requests.end());

  struct address_data request_data;
  request_data.address = Address(m(index));
  request_data.data = write_data;
  m_driver.requests.insert(make_pair(request_id, make_pair(m_proc_id, request_data)));  

  /*sequencer()->makeRequest(CacheMsg(Address(m(index)), Address(m(index)), CacheRequestType_ST,
                                    Address(physical_address_t(1)),
                                    AccessModeType_UserMode, 4,
                                    PrefetchBit_No, 0, Address(0),
                                    0, 0, false)); */
}

// Save and Load context of a thread
void RaceyPseudoThread::saveCPUStates(string filename) {
  ogzstream out(filename.c_str());
  out.write((char*)&m_looper, sizeof(int));
  out.write((char*)&m_num, sizeof(unsigned));
  out.write((char*)&m_index1, sizeof(unsigned));
  out.write((char*)&m_index2, sizeof(unsigned));
  out.write((char*)&m_stop, sizeof(unsigned));
  out.close();
}

void RaceyPseudoThread::loadCPUStates(string filename) {
  igzstream out(filename.c_str());
  out.read((char*)&m_looper, sizeof(int));
  out.read((char*)&m_num, sizeof(unsigned));
  out.read((char*)&m_index1, sizeof(unsigned));
  out.read((char*)&m_index2, sizeof(unsigned));
  out.read((char*)&m_stop, sizeof(unsigned));
  out.close();
}

void RaceyPseudoThread::print(ostream& out) const {
  out << "[Racey Pseudo Thread: " << m_proc_id << "]" << endl;
}