void
reportDisconnect(void* callbackObj, NodeId nodeId, Uint32 errNo){
  char buf[255];
  sprintf(buf, "reportDisconnect(%d)", nodeId);
  ndbout << buf << endl;

  if(isStarted)
    tReg->setPerformState(nodeId, PerformConnect);
}

/*
   Use is subject to license terms

   */

#include <ndb_global.h>

#include "TransporterRegistry.hpp"
#include "TransporterDefinitions.hpp"
#include "TransporterCallback.hpp"
#include <RefConvert.hpp>

#include <NdbTick.h>
#include <NdbMain.h>
#include <NdbOut.hpp>
#include <NdbSleep.h>

int basePortTCP = 17000;

SCI_TransporterConfiguration sciTemplate = {
  2000,
       // Packet size
  2000000,      // Buffer size
  2,           // number of adapters
  1,           // remote node id SCI
  2,           // Remote node Id SCI
  0,           // local ndb node id (server)
  0,           // remote ndb node id (client)
  0,              // byteOrder;
  false,          // compression;
  true,          // checksum;
  true            // signalId;
};

SHM_TransporterConfiguration shmTemplate = {
  0,      //remoteNodeId
  0,      //localNodeId;
  false,  //compression
  true,   //checksum;
  true,   //signalId;
  0,      //byteOrder;
  123,    //shmKey;
  25000000 //shmSize;
};

TCP_TransporterConfiguration tcpTemplate = {
  17000,          // port;
  "",             // remoteHostName;
  "",             // localhostname
  2,              // remoteNodeId;
  1,              // localNodeId;
  25000000,        // sendBufferSize - Size of SendBuffer of priority B
  5000000,         // maxReceiveSize - Maximum no of bytes to receive
  0,              // byteOrder;
  false,          // compression;
  true,           // checksum;
  true            // signalId;
};

TransporterRegistry *tReg = 0;

#include <signal.h>

extern "C"
void
signalHandler(int signo){
  ::signal(13, signalHandler);
  char buf[255];
  sprintf(buf,"Signal: %d\n", signo);
  ndbout << buf << endl;
}

void
usage(const char * progName){
  ndbout << "Usage: " << progName << " <type> localNodeId localHostName"
	 << " remoteHostName"
	 << " [<loop count>] [<send buf size>] [<recv buf size>]" << endl;
  ndbout << "  type = shm tcp ose sci" << endl;
  ndbout << "  localNodeId - {1,2}" << endl;
}

typedef void (* CreateTransporterFunc)(void * conf,
				       NodeId localNodeId,
				       NodeId remoteNodeId,
				       const char * localHostName,
				       const char * remoteHostName,
				       int sendBuf,
				       int recvBuf);

void
createTCPTransporter(void*, NodeId, NodeId, const char*, const char*, int, int);
void
createSHMTransporter(void*, NodeId, NodeId, const char*, const char*, int, int);
void
createSCITransporter(void*, NodeId, NodeId, const char*, const char*, int, int);

struct TestPhase {
  int signalSize;
  int noOfSignals;
  int noOfSignalSent;
  int noOfSignalReceived;
  NDB_TICKS startTime;
  NDB_TICKS stopTime;
  NDB_TICKS accTime;
  int loopCount;
  Uint64 sendLenBytes, sendCount;
  Uint64 recvLenBytes, recvCount;
};

TestPhase testSpec[] = {
   {  1,    10, 0,0, 0,0,0,0,0,0,0 } //    10 signals of size 1  word
  ,{  1,   100, 0,0, 0,0,0,0,0,0,0 } //   100 signals of size 1  word
  ,{  1,  1000, 0,0, 0,0,0,0,0,0,0 } //  1000 signals of size 1  word
  ,{  1, 10000, 0,0, 0,0,0,0,0,0,0 } // 10000 signals of size 1  word

  ,{  8,    10, 0,0, 0,0,0,0,0,0,0 } //    10 signals of size 1  word
  ,{  8,   100, 0,0, 0,0,0,0,0,0,0 } //   100 signals of size 1  word
  ,{  8,  1000, 0,0, 0,0,0,0,0,0,0 } //  1000 signals of size 1  word
  ,{  8, 10000, 0,0, 0,0,0,0,0,0,0 } // 10000 signals of size 1  word

  ,{ 16,    10, 0,0, 0,0,0,0,0,0,0 } //    10 signals of size 1  word
  ,{ 16,   100, 0,0, 0,0,0,0,0,0,0 } //   100 signals of size 1  word
  ,{ 16,  1000, 0,0, 0,0,0,0,0,0,0 } //  1000 signals of size 1  word
  ,{ 16, 10000, 0,0, 0,0,0,0,0,0,0 } // 10000 signals of size 1  word

  ,{ 24,    10, 0,0, 0,0,0,0,0,0,0 } //    10 signals of size 1  word
  ,{ 24,   100, 0,0, 0,0,0,0,0,0,0 } //   100 signals of size 1  word
  ,{ 24,  1000, 0,0, 0,0,0,0,0,0,0 } //  1000 signals of size 1  word
  ,{ 24, 10000, 0,0, 0,0,0,0,0,0,0 } // 10000 signals of size 1  word

  ,{  0,    10, 0,0, 0,0,0,0,0,0,0 } //    10 signals of random size
  ,{  0,   100, 0,0, 0,0,0,0,0,0,0 } //   100 signals of random size
  ,{  0,  1000, 0,0, 0,0,0,0,0,0,0 } //  1000 signals of random size
  ,{  0, 10000, 0,0, 0,0,0,0,0,0,0 } // 10000 signals of random size

  ,{ 100,    10, 0,0, 0,0,0,0,0,0,0 } //    10 signals
  ,{ 100,   100, 0,0, 0,0,0,0,0,0,0 } //   100 signals
  ,{ 100,  1000, 0,0, 0,0,0,0,0,0,0 } //  1000 signals
  ,{ 100, 10000, 0,0, 0,0,0,0,0,0,0 } // 10000 signals

  ,{ 500,    10, 0,0, 0,0,0,0,0,0,0 } //    10 signals
  ,{ 500,   100, 0,0, 0,0,0,0,0,0,0 } //   100 signals
  ,{ 500,  1000, 0,0, 0,0,0,0,0,0,0 } //  1000 signals
  ,{ 500, 10000, 0,0, 0,0,0,0,0,0,0 } // 10000 signals

  ,{ 1000,    10, 0,0, 0,0,0,0,0,0,0 } //    10 signals
  ,{ 1000,   100, 0,0, 0,0,0,0,0,0,0 } //   100 signals
  ,{ 1000,  1000, 0,0, 0,0,0,0,0,0,0 } //  1000 signals
  ,{ 1000, 10000, 0,0, 0,0,0,0,0,0,0 } // 10000 signals
};

const int noOfTests = sizeof(testSpec)/sizeof(TestPhase);

Uint32 StaticBuffer[1000];

SendStatus
sendSignalTo(NodeId nodeId, int signalSize, Uint32 count){
  if(signalSize == 0)
    signalSize = (rand() % 25) + 1;

  SignalHeader sh;
  sh.theLength               = (signalSize > 25 ? 25 : signalSize);
  sh.theVerId_signalNumber   = count;
  sh.theReceiversBlockNumber = rand();
  sh.theSendersBlockRef      = rand();
  sh.theSendersSignalId      = rand();
  sh.theSignalId             = rand();
  sh.theTrace                = rand();

  Uint32 theData[25];
  for(int i = 0; i<25; i++)
    theData[i] = (i+1) * (Uint32)(&theData[i]);

  theData[0] = count;
  LinearSectionPtr ptr[3];

  if(signalSize <= 25){
    sh.m_noOfSections = 0;
  } else {
    sh.m_noOfSections = 1;
    ptr[0].sz = signalSize - 25;
    ptr[0].p = &StaticBuffer[0];
  }

  return tReg->prepareSend(&sh, 1, theData, nodeId, ptr);
}

void
reportHeader(){
  ndbout << "#Sigs\tSz\tTime\tSig/sec\tBps\tBps-tot\t"
	 << "s len\tr len" << endl;
}

void
print(char * dst, int i){
  if(i > 1000000){
    const int d = i / 1000000;
    const int r = (i - (d * 1000000)) / 100000;
    if(d < 100)
      sprintf(dst, "%d.%dM", d, r);
    else
      sprintf(dst, "%dM", d);
  } else if(i > 1000){
    const int d = i / 1000;
    const int r = (i - (d * 1000)) / 100;
    if(d < 100)
      sprintf(dst, "%d.%dk", d, r);
    else
      sprintf(dst, "%dk", d);
  } else {
    sprintf(dst, "%d", i);
  }
}

void
printReport(TestPhase & p){
  if(p.accTime > 0) {
    Uint32 secs = (p.accTime/p.loopCount)/1000;
    Uint32 mill = (p.accTime/p.loopCount)%1000;
    char st[255];
    if(secs > 0){
      sprintf(st, "%d.%.2ds", secs, (mill/10));
    } else {
      sprintf(st, "%dms", mill);
    }

    Uint32 sps = (1000*p.noOfSignals*p.loopCount)/p.accTime;
    Uint32 dps = ((4000*p.noOfSignals)/p.accTime)*(p.loopCount*p.signalSize);
    Uint32 bps = ((4000*p.noOfSignals)/p.accTime)*(p.loopCount*(p.signalSize+3));
    if(p.signalSize == 0){
      dps = ((4000*p.noOfSignals)/p.accTime)*(p.loopCount*(13));
      bps = ((4000*p.noOfSignals)/p.accTime)*(p.loopCount*(13+3));
    }
    char ssps[255];
    char sbps[255];
    char sdps[255];

    print(ssps, sps);
    print(sbps, bps);
    print(sdps, dps);

    char buf[255];
    if(p.signalSize != 0){
      BaseString::snprintf(buf, 255,
	       "%d\t%d\t%s\t%s\t%s\t%s\t%d\t%d",
	       p.noOfSignals,
	       4*p.signalSize,
	       st,
	       ssps,
	       sdps,
	       sbps,
	       (int)(p.sendLenBytes / (p.sendCount == 0 ? 1 : p.sendCount)),
	       (int)(p.recvLenBytes / (p.recvCount == 0 ? 1 : p.recvCount)));
    } else {
      BaseString::snprintf(buf, 255,
	       "%d\trand\t%s\t%s\t%s\t%s\t%d\t%d",
	       p.noOfSignals,
	       st,
	       ssps,
	       sdps,
	       sbps,
	       (int)(p.sendLenBytes / (p.sendCount == 0 ? 1 : p.sendCount)),
	       (int)(p.recvLenBytes / (p.recvCount == 0 ? 1 : p.recvCount)));

    }
    ndbout << buf << endl;
  }
}

int loopCount = 1;
int sendBufSz = -1;
int recvBufSz = -1;

bool      isClient     = false;
bool      isConnected  = false;
bool      isStarted    = false;
int       currentPhase = 0;
TestPhase allPhases[noOfTests];
Uint32    signalToEcho;
Uint32    signalsEchoed;
NDB_TICKS startTime, stopTime;

void
client(NodeId remoteNodeId){
  isClient = true;

  currentPhase = 0;
  memcpy(allPhases, testSpec, sizeof(testSpec));

  int counter = 0;
  int sigCounter = 0;

  while(true){
    TestPhase * current = &allPhases[currentPhase];
    if(current->noOfSignals == current->noOfSignalSent &&
       current->noOfSignals == current->noOfSignalReceived){

      /**
       * Test phase done
       */
      current->stopTime  = NdbTick_CurrentMillisecond();
      current->accTime  += (current->stopTime - current->startTime);

      NdbSleep_MilliSleep(500 / loopCount);

      current->startTime = NdbTick_CurrentMillisecond();

      current->noOfSignalSent     = 0;
      current->noOfSignalReceived = 0;

      current->loopCount ++;
      if(current->loopCount == loopCount){

	printReport(allPhases[currentPhase]);

	currentPhase ++;
	if(currentPhase == noOfTests){
	  /**
	   * Now we are done
	   */
	  break;
	}
	NdbSleep_MilliSleep(500);
	current = &allPhases[currentPhase];
	current->startTime = NdbTick_CurrentMillisecond();
      }
    }

    int signalsLeft = current->noOfSignals - current->noOfSignalSent;
    if(signalsLeft > 0){
      for(; signalsLeft > 0; signalsLeft--){
	if(sendSignalTo(remoteNodeId,current->signalSize,sigCounter)== SEND_OK){
	  current->noOfSignalSent++;
	  sigCounter++;
	} else {
	  ndbout << "Failed to send: " << sigCounter << endl;
	  tReg->external_IO(10);
	  break;
	}
      }
    }
    if(counter % 10 == 0)
      tReg->checkConnections();
    tReg->external_IO(0);
    counter++;
  }
}

void
server(){
  isClient = false;

  signalToEcho = 0;
  signalsEchoed = 0;
  for(int i = 0; i<noOfTests; i++)
    signalToEcho += testSpec[i].noOfSignals;

  signalToEcho *= loopCount;

  while(signalToEcho > signalsEchoed){
    tReg->checkConnections();
    for(int i = 0; i<10; i++)
      tReg->external_IO(10);
  }
}

int
main(int argc, const char **argv){

  const char * progName = argv[0];

  loopCount = 100;
  sendBufSz = -1;
  recvBufSz = -1;

  isClient     = false;
  isConnected  = false;
  isStarted    = false;
  currentPhase = 0;

  signalHandler(0);

  if(argc < 5){
    usage(progName);
    return 0;
  }

  const char * type = argv[1];
  const NodeId localNodeId   = atoi(argv[2]);
  const char * localHostName = argv[3];
  const char * remoteHost1   = argv[4];

  if(argc >= 6)
    loopCount = atoi(argv[5]);
  if(argc >= 7)
    sendBufSz = atoi(argv[6]);
  if(argc >= 8)
    recvBufSz = atoi(argv[7]);

  if(localNodeId < 1 || localNodeId > 2){
    ndbout << "localNodeId = " << localNodeId << endl << endl;
    usage(progName);
    return 0;
  }

  if(localNodeId == 1)
    ndbout << "-- ECHO CLIENT --" << endl;
  else
    ndbout << "-- ECHO SERVER --" << endl;

  ndbout << "localNodeId:           " << localNodeId << endl;
  ndbout << "localHostName:         " << localHostName << endl;
  ndbout << "remoteHost1 (node " << (localNodeId == 1?2:1) << "): "
	 << remoteHost1 << endl;
  ndbout << "Loop count: " << loopCount << endl;
  ndbout << "-----------------" << endl;

  void * confTemplate = 0;
  CreateTransporterFunc func = 0;
  if(strcasecmp(type, "tcp") == 0){
    func = createTCPTransporter;
    confTemplate = &tcpTemplate;
  } else if(strcasecmp(type, "sci") == 0){
    func = createSCITransporter;
    confTemplate = &sciTemplate;
  } else if(strcasecmp(type, "shm") == 0){
    func = createSHMTransporter;
    confTemplate = &shmTemplate;
  } else {
    ndbout << "Unsupported transporter type" << endl;
    return 0;
  }

  ndbout << "Creating transporter registry" << endl;
  tReg = new TransporterRegistry;
  tReg->init(localNodeId);

  switch(localNodeId){
  case 1:
    (* func)(confTemplate, 1, 2, localHostName, remoteHost1,
	     sendBufSz, recvBufSz);
    break;
  case 2:
    (* func)(confTemplate, 2, 1, localHostName, remoteHost1,
	     sendBufSz, recvBufSz);
    break;
  }

  ndbout << "Doing startSending/startReceiving" << endl;
  tReg->startSending();
  tReg->startReceiving();

  ndbout << "Connecting" << endl;
  tReg->setPerformState(PerformConnect);
  tReg->checkConnections();

  if(localNodeId == 1)
    client(2);
  else
    server();

  isStarted = false;

  ndbout << "Sleep 3 secs" << endl;
  NdbSleep_SecSleep(3);

  ndbout << "Doing setPerformState(Disconnect)" << endl;
  tReg->setPerformState(PerformDisconnect);

  ndbout << "Doing checkConnections()" << endl;
  tReg->checkConnections();

  ndbout << "Deleting transporter registry" << endl;
  delete tReg; tReg = 0;

  return 0;
}

void
execute(void* callbackObj, SignalHeader * const header, Uint8 prio,
	Uint32 * const theData,
	LinearSectionPtr ptr[3]){
  const NodeId nodeId = refToNode(header->theSendersBlockRef);

  if(isClient){
    allPhases[currentPhase].noOfSignalReceived++;
  } else {
    int sleepTime = 10;
    if(theData[0] != signalsEchoed){
      ndbout << "Missing signal theData[0] = " << theData[0]
	     << " signalsEchoed = " << signalsEchoed << endl;
      ndbout << (* header) << endl;
      abort();
    }
    while(tReg->prepareSend(header, prio, theData, nodeId, ptr) != SEND_OK){
      ndbout << "Failed to echo " << theData[0] << endl;
      NdbSleep_MilliSleep(sleepTime);
    }
    signalsEchoed++;
  }
}

void
copy(Uint32 * & insertPtr,
     class SectionSegmentPool & thePool, const SegmentedSectionPtr & _ptr){
  abort();
}

void
reportError(void* callbackObj, NodeId nodeId, TransporterError errorCode){
  char buf[255];
  sprintf(buf, "reportError (%d, %x) in perfTest", nodeId, errorCode);
  ndbout << buf << endl;
  if(errorCode & 0x8000 && errorCode != 0x8014){
    abort(); //tReg->setPerformState(nodeId, PerformDisconnect);
  }
}

/**
 * Report average send theLength in bytes (4096 last sends)
 */
void
reportSendLen(void* callbackObj, NodeId nodeId, Uint32 count, Uint64 bytes){
  allPhases[currentPhase].sendCount    += count;
  allPhases[currentPhase].sendLenBytes += bytes;

  if(!isClient){
    ndbout << "reportSendLen(" << nodeId << ", "
	   << (bytes/count) << ")" << endl;
  }
}

/**
 * Report average receive theLength in bytes (4096 last receives)
 */
void
reportReceiveLen(void* callbackObj, NodeId nodeId, Uint32 count, Uint64 bytes){
  allPhases[currentPhase].recvCount    += count;
  allPhases[currentPhase].recvLenBytes += bytes;

  if(!isClient){
    ndbout << "reportReceiveLen(" << nodeId << ", "
	   << (bytes/count) << ")" << endl;
  }
}

/**
 * Report connection established
 */
void
reportConnect(void* callbackObj, NodeId nodeId){
  char buf[255];
  sprintf(buf, "reportConnect(%d)", nodeId);
  ndbout << buf << endl;
  tReg->setPerformState(nodeId, PerformIO);

  if(!isStarted){
    isStarted = true;
    startTime = NdbTick_CurrentMillisecond();
    if(isClient){
      reportHeader();
      allPhases[0].startTime = startTime;
    }
  }
  else{
    // Resend signals that were lost when connection failed
    TestPhase * current = &allPhases[currentPhase];
    current->noOfSignalSent = current->noOfSignalReceived;
  }
}

/**
 * Report connection broken
 */

int
checkJobBuffer() {
  /**
   * Check to see if jobbbuffers are starting to get full
   * and if so call doJob
   */
  return 0;
}

void
createSCITransporter(void * _conf,
		     NodeId localNodeId,
		     NodeId remoteNodeId,
		     const char * localHostName,
		     const char * remoteHostName,
		     int sendbuf,
		     int recvbuf) {

  ndbout << "Creating SCI transporter from node "
	 << localNodeId << "(" << localHostName << ") to "
	 << remoteNodeId << "(" << remoteHostName << ")..." << endl;;

  SCI_TransporterConfiguration * conf = (SCI_TransporterConfiguration*)_conf;

  conf->remoteSciNodeId0= (Uint16)atoi(localHostName);
  conf->remoteSciNodeId1= (Uint16)atoi(remoteHostName);

  conf->localNodeId    = localNodeId;
  conf->remoteNodeId   = remoteNodeId;

  bool res = tReg->createTransporter(conf);
  if(res)
    ndbout << "... -- Success " << endl;
  else
    ndbout << "... -- Failure " << endl;
}

void
createSHMTransporter(void * _conf,
		     NodeId localNodeId,
		     NodeId remoteNodeId,
		     const char * localHostName,
		     const char * remoteHostName,
		     int sendbuf,
		     int recvbuf) {

  ndbout << "Creating SHM transporter from node "
	 << localNodeId << "(" << localHostName << ") to "
	 << remoteNodeId << "(" << remoteHostName << ")..." << endl;;

  SHM_TransporterConfiguration * conf = (SHM_TransporterConfiguration*)_conf;

  conf->localNodeId    = localNodeId;
  conf->remoteNodeId   = remoteNodeId;

  bool res = tReg->createTransporter(conf);
  if(res)
    ndbout << "... -- Success " << endl;
  else
    ndbout << "... -- Failure " << endl;
}

void
createTCPTransporter(void * _conf,
		     NodeId localNodeId,
		     NodeId remoteNodeId,
		     const char * localHostName,
		     const char * remoteHostName,
		     int sendBuf,
		     int recvBuf){
  ndbout << "Creating TCP transporter from node "
	 << localNodeId << "(" << localHostName << ") to "
	 << remoteNodeId << "(" << remoteHostName << ")..." << endl;;

  TCP_TransporterConfiguration * conf = (TCP_TransporterConfiguration*)_conf;

  int port;
  if(localNodeId == 1 && remoteNodeId == 2) port = basePortTCP + 0;
  if(localNodeId == 1 && remoteNodeId == 3) port = basePortTCP + 1;
  if(localNodeId == 2 && remoteNodeId == 1) port = basePortTCP + 0;
  if(localNodeId == 2 && remoteNodeId == 3) port = basePortTCP + 2;
  if(localNodeId == 3 && remoteNodeId == 1) port = basePortTCP + 1;
  if(localNodeId == 3 && remoteNodeId == 2) port = basePortTCP + 2;

  if(sendBuf != -1){
    conf->sendBufferSize = sendBuf;
  }
  if(recvBuf != -1){
    conf->maxReceiveSize = recvBuf;
  }

  ndbout << "\tSendBufferSize:    " << conf->sendBufferSize << endl;
  ndbout << "\tReceiveBufferSize: " << conf->maxReceiveSize << endl;

  conf->localNodeId    = localNodeId;
  conf->localHostName  = localHostName;
  conf->remoteNodeId   = remoteNodeId;
  conf->remoteHostName = remoteHostName;
  conf->port           = port;
  bool res = tReg->createTransporter(conf);
  if(res)
    ndbout << "... -- Success " << endl;
  else
    ndbout << "... -- Failure " << endl;
}


switch(localNodeId){
  case 1:
    (* func)(confTemplate, 1, 2, localHostName, remoteHost1,
	     sendBufSz, recvBufSz);
    break;
  case 2:
    (* func)(confTemplate, 2, 1, localHostName, remoteHost1,
	     sendBufSz, recvBufSz);
    break;
  }

/*
   Use is subject to license terms

   */

#include <ndb_global.h>

#include "TransporterRegistry.hpp"
#include "TransporterDefinitions.hpp"
#include "TransporterCallback.hpp"
#include <RefConvert.hpp>

#include <NdbTick.h>
#include <NdbMain.h>
#include <NdbOut.hpp>
#include <NdbSleep.h>

int basePortTCP = 17000;

SCI_TransporterConfiguration sciTemplate = {
  2000,
       // Packet size
  2000000,      // Buffer size
  2,           // number of adapters
  1,           // remote node id SCI
  2,           // Remote node Id SCI
  0,           // local ndb node id (server)
  0,           // remote ndb node id (client)
  0,              // byteOrder;
  false,          // compression;
  true,          // checksum;
  true            // signalId;
};

SHM_TransporterConfiguration shmTemplate = {
  0,      //remoteNodeId
  0,      //localNodeId;
  false,  //compression
  true,   //checksum;
  true,   //signalId;
  0,      //byteOrder;
  123,    //shmKey;
  25000000 //shmSize;
};

TCP_TransporterConfiguration tcpTemplate = {
  17000,          // port;
  "",             // remoteHostName;
  "",             // localhostname
  2,              // remoteNodeId;
  1,              // localNodeId;
  25000000,        // sendBufferSize - Size of SendBuffer of priority B
  5000000,         // maxReceiveSize - Maximum no of bytes to receive
  0,              // byteOrder;
  false,          // compression;
  true,           // checksum;
  true            // signalId;
};

TransporterRegistry *tReg = 0;

#include <signal.h>

extern "C"
void
signalHandler(int signo){
  ::signal(13, signalHandler);
  char buf[255];
  sprintf(buf,"Signal: %d\n", signo);
  ndbout << buf << endl;
}

void
usage(const char * progName){
  ndbout << "Usage: " << progName << " <type> localNodeId localHostName"
	 << " remoteHostName"
	 << " [<loop count>] [<send buf size>] [<recv buf size>]" << endl;
  ndbout << "  type = shm tcp ose sci" << endl;
  ndbout << "  localNodeId - {1,2}" << endl;
}

typedef void (* CreateTransporterFunc)(void * conf,
				       NodeId localNodeId,
				       NodeId remoteNodeId,
				       const char * localHostName,
				       const char * remoteHostName,
				       int sendBuf,
				       int recvBuf);

void
createTCPTransporter(void*, NodeId, NodeId, const char*, const char*, int, int);
void
createSHMTransporter(void*, NodeId, NodeId, const char*, const char*, int, int);
void
createSCITransporter(void*, NodeId, NodeId, const char*, const char*, int, int);

struct TestPhase {
  int signalSize;
  int noOfSignals;
  int noOfSignalSent;
  int noOfSignalReceived;
  NDB_TICKS startTime;
  NDB_TICKS stopTime;
  NDB_TICKS accTime;
  int loopCount;
  Uint64 sendLenBytes, sendCount;
  Uint64 recvLenBytes, recvCount;
};

TestPhase testSpec[] = {
   {  1,    10, 0,0, 0,0,0,0,0,0,0 } //    10 signals of size 1  word
  ,{  1,   100, 0,0, 0,0,0,0,0,0,0 } //   100 signals of size 1  word
  ,{  1,  1000, 0,0, 0,0,0,0,0,0,0 } //  1000 signals of size 1  word
  ,{  1, 10000, 0,0, 0,0,0,0,0,0,0 } // 10000 signals of size 1  word

  ,{  8,    10, 0,0, 0,0,0,0,0,0,0 } //    10 signals of size 1  word
  ,{  8,   100, 0,0, 0,0,0,0,0,0,0 } //   100 signals of size 1  word
  ,{  8,  1000, 0,0, 0,0,0,0,0,0,0 } //  1000 signals of size 1  word
  ,{  8, 10000, 0,0, 0,0,0,0,0,0,0 } // 10000 signals of size 1  word

  ,{ 16,    10, 0,0, 0,0,0,0,0,0,0 } //    10 signals of size 1  word
  ,{ 16,   100, 0,0, 0,0,0,0,0,0,0 } //   100 signals of size 1  word
  ,{ 16,  1000, 0,0, 0,0,0,0,0,0,0 } //  1000 signals of size 1  word
  ,{ 16, 10000, 0,0, 0,0,0,0,0,0,0 } // 10000 signals of size 1  word

  ,{ 24,    10, 0,0, 0,0,0,0,0,0,0 } //    10 signals of size 1  word
  ,{ 24,   100, 0,0, 0,0,0,0,0,0,0 } //   100 signals of size 1  word
  ,{ 24,  1000, 0,0, 0,0,0,0,0,0,0 } //  1000 signals of size 1  word
  ,{ 24, 10000, 0,0, 0,0,0,0,0,0,0 } // 10000 signals of size 1  word

  ,{  0,    10, 0,0, 0,0,0,0,0,0,0 } //    10 signals of random size
  ,{  0,   100, 0,0, 0,0,0,0,0,0,0 } //   100 signals of random size
  ,{  0,  1000, 0,0, 0,0,0,0,0,0,0 } //  1000 signals of random size
  ,{  0, 10000, 0,0, 0,0,0,0,0,0,0 } // 10000 signals of random size

  ,{ 100,    10, 0,0, 0,0,0,0,0,0,0 } //    10 signals
  ,{ 100,   100, 0,0, 0,0,0,0,0,0,0 } //   100 signals
  ,{ 100,  1000, 0,0, 0,0,0,0,0,0,0 } //  1000 signals
  ,{ 100, 10000, 0,0, 0,0,0,0,0,0,0 } // 10000 signals

  ,{ 500,    10, 0,0, 0,0,0,0,0,0,0 } //    10 signals
  ,{ 500,   100, 0,0, 0,0,0,0,0,0,0 } //   100 signals
  ,{ 500,  1000, 0,0, 0,0,0,0,0,0,0 } //  1000 signals
  ,{ 500, 10000, 0,0, 0,0,0,0,0,0,0 } // 10000 signals

  ,{ 1000,    10, 0,0, 0,0,0,0,0,0,0 } //    10 signals
  ,{ 1000,   100, 0,0, 0,0,0,0,0,0,0 } //   100 signals
  ,{ 1000,  1000, 0,0, 0,0,0,0,0,0,0 } //  1000 signals
  ,{ 1000, 10000, 0,0, 0,0,0,0,0,0,0 } // 10000 signals
};

const int noOfTests = sizeof(testSpec)/sizeof(TestPhase);

Uint32 StaticBuffer[1000];

SendStatus
sendSignalTo(NodeId nodeId, int signalSize, Uint32 count){
  if(signalSize == 0)
    signalSize = (rand() % 25) + 1;

  SignalHeader sh;
  sh.theLength               = (signalSize > 25 ? 25 : signalSize);
  sh.theVerId_signalNumber   = count;
  sh.theReceiversBlockNumber = rand();
  sh.theSendersBlockRef      = rand();
  sh.theSendersSignalId      = rand();
  sh.theSignalId             = rand();
  sh.theTrace                = rand();

  Uint32 theData[25];
  for(int i = 0; i<25; i++)
    theData[i] = (i+1) * (Uint32)(&theData[i]);

  theData[0] = count;
  LinearSectionPtr ptr[3];

  if(signalSize <= 25){
    sh.m_noOfSections = 0;
  } else {
    sh.m_noOfSections = 1;
    ptr[0].sz = signalSize - 25;
    ptr[0].p = &StaticBuffer[0];
  }

  return tReg->prepareSend(&sh, 1, theData, nodeId, ptr);
}

void
reportHeader(){
  ndbout << "#Sigs\tSz\tTime\tSig/sec\tBps\tBps-tot\t"
	 << "s len\tr len" << endl;
}

void
print(char * dst, int i){
  if(i > 1000000){
    const int d = i / 1000000;
    const int r = (i - (d * 1000000)) / 100000;
    if(d < 100)
      sprintf(dst, "%d.%dM", d, r);
    else
      sprintf(dst, "%dM", d);
  } else if(i > 1000){
    const int d = i / 1000;
    const int r = (i - (d * 1000)) / 100;
    if(d < 100)
      sprintf(dst, "%d.%dk", d, r);
    else
      sprintf(dst, "%dk", d);
  } else {
    sprintf(dst, "%d", i);
  }
}

void
printReport(TestPhase & p){
  if(p.accTime > 0) {
    Uint32 secs = (p.accTime/p.loopCount)/1000;
    Uint32 mill = (p.accTime/p.loopCount)%1000;
    char st[255];
    if(secs > 0){
      sprintf(st, "%d.%.2ds", secs, (mill/10));
    } else {
      sprintf(st, "%dms", mill);
    }

    Uint32 sps = (1000*p.noOfSignals*p.loopCount)/p.accTime;
    Uint32 dps = ((4000*p.noOfSignals)/p.accTime)*(p.loopCount*p.signalSize);
    Uint32 bps = ((4000*p.noOfSignals)/p.accTime)*(p.loopCount*(p.signalSize+3));
    if(p.signalSize == 0){
      dps = ((4000*p.noOfSignals)/p.accTime)*(p.loopCount*(13));
      bps = ((4000*p.noOfSignals)/p.accTime)*(p.loopCount*(13+3));
    }
    char ssps[255];
    char sbps[255];
    char sdps[255];

    print(ssps, sps);
    print(sbps, bps);
    print(sdps, dps);

    char buf[255];
    if(p.signalSize != 0){
      BaseString::snprintf(buf, 255,
	       "%d\t%d\t%s\t%s\t%s\t%s\t%d\t%d",
	       p.noOfSignals,
	       4*p.signalSize,
	       st,
	       ssps,
	       sdps,
	       sbps,
	       (int)(p.sendLenBytes / (p.sendCount == 0 ? 1 : p.sendCount)),
	       (int)(p.recvLenBytes / (p.recvCount == 0 ? 1 : p.recvCount)));
    } else {
      BaseString::snprintf(buf, 255,
	       "%d\trand\t%s\t%s\t%s\t%s\t%d\t%d",
	       p.noOfSignals,
	       st,
	       ssps,
	       sdps,
	       sbps,
	       (int)(p.sendLenBytes / (p.sendCount == 0 ? 1 : p.sendCount)),
	       (int)(p.recvLenBytes / (p.recvCount == 0 ? 1 : p.recvCount)));

    }
    ndbout << buf << endl;
  }
}

int loopCount = 1;
int sendBufSz = -1;
int recvBufSz = -1;

bool      isClient     = false;
bool      isConnected  = false;
bool      isStarted    = false;
int       currentPhase = 0;
TestPhase allPhases[noOfTests];
Uint32    signalToEcho;
Uint32    signalsEchoed;
NDB_TICKS startTime, stopTime;

void
client(NodeId remoteNodeId){
  isClient = true;

  currentPhase = 0;
  memcpy(allPhases, testSpec, sizeof(testSpec));

  int counter = 0;
  int sigCounter = 0;

  while(true){
    TestPhase * current = &allPhases[currentPhase];
    if(current->noOfSignals == current->noOfSignalSent &&
       current->noOfSignals == current->noOfSignalReceived){

      /**
       * Test phase done
       */
      current->stopTime  = NdbTick_CurrentMillisecond();
      current->accTime  += (current->stopTime - current->startTime);

      NdbSleep_MilliSleep(500 / loopCount);

      current->startTime = NdbTick_CurrentMillisecond();

      current->noOfSignalSent     = 0;
      current->noOfSignalReceived = 0;

      current->loopCount ++;
      if(current->loopCount == loopCount){

	printReport(allPhases[currentPhase]);

	currentPhase ++;
	if(currentPhase == noOfTests){
	  /**
	   * Now we are done
	   */
	  break;
	}
	NdbSleep_MilliSleep(500);
	current = &allPhases[currentPhase];
	current->startTime = NdbTick_CurrentMillisecond();
      }
    }

    int signalsLeft = current->noOfSignals - current->noOfSignalSent;
    if(signalsLeft > 0){
      for(; signalsLeft > 0; signalsLeft--){
	if(sendSignalTo(remoteNodeId,current->signalSize,sigCounter)== SEND_OK){
	  current->noOfSignalSent++;
	  sigCounter++;
	} else {
	  ndbout << "Failed to send: " << sigCounter << endl;
	  tReg->external_IO(10);
	  break;
	}
      }
    }
    if(counter % 10 == 0)
      tReg->checkConnections();
    tReg->external_IO(0);
    counter++;
  }
}

void
server(){
  isClient = false;

  signalToEcho = 0;
  signalsEchoed = 0;
  for(int i = 0; i<noOfTests; i++)
    signalToEcho += testSpec[i].noOfSignals;

  signalToEcho *= loopCount;

  while(signalToEcho > signalsEchoed){
    tReg->checkConnections();
    for(int i = 0; i<10; i++)
      tReg->external_IO(10);
  }
}

int
main(int argc, const char **argv){

  const char * progName = argv[0];

  loopCount = 100;
  sendBufSz = -1;
  recvBufSz = -1;

  isClient     = false;
  isConnected  = false;
  isStarted    = false;
  currentPhase = 0;

  signalHandler(0);

  if(argc < 5){
    usage(progName);
    return 0;
  }

  const char * type = argv[1];
  const NodeId localNodeId   = atoi(argv[2]);
  const char * localHostName = argv[3];
  const char * remoteHost1   = argv[4];

  if(argc >= 6)
    loopCount = atoi(argv[5]);
  if(argc >= 7)
    sendBufSz = atoi(argv[6]);
  if(argc >= 8)
    recvBufSz = atoi(argv[7]);

  if(localNodeId < 1 || localNodeId > 2){
    ndbout << "localNodeId = " << localNodeId << endl << endl;
    usage(progName);
    return 0;
  }

  if(localNodeId == 1)
    ndbout << "-- ECHO CLIENT --" << endl;
  else
    ndbout << "-- ECHO SERVER --" << endl;

  ndbout << "localNodeId:           " << localNodeId << endl;
  ndbout << "localHostName:         " << localHostName << endl;
  ndbout << "remoteHost1 (node " << (localNodeId == 1?2:1) << "): "
	 << remoteHost1 << endl;
  ndbout << "Loop count: " << loopCount << endl;
  ndbout << "-----------------" << endl;

  void * confTemplate = 0;
  CreateTransporterFunc func = 0;
  if(strcasecmp(type, "tcp") == 0){
    func = createTCPTransporter;
    confTemplate = &tcpTemplate;
  } else if(strcasecmp(type, "sci") == 0){
    func = createSCITransporter;
    confTemplate = &sciTemplate;
  } else if(strcasecmp(type, "shm") == 0){
    func = createSHMTransporter;
    confTemplate = &shmTemplate;
  } else {
    ndbout << "Unsupported transporter type" << endl;
    return 0;
  }

  ndbout << "Creating transporter registry" << endl;
  tReg = new TransporterRegistry;
  tReg->init(localNodeId);

  ndbout << "Doing startSending/startReceiving" << endl;
  tReg->startSending();
  tReg->startReceiving();

  ndbout << "Connecting" << endl;
  tReg->setPerformState(PerformConnect);
  tReg->checkConnections();

  if(localNodeId == 1)
    client(2);
  else
    server();

  isStarted = false;

  ndbout << "Sleep 3 secs" << endl;
  NdbSleep_SecSleep(3);

  ndbout << "Doing setPerformState(Disconnect)" << endl;
  tReg->setPerformState(PerformDisconnect);

  ndbout << "Doing checkConnections()" << endl;
  tReg->checkConnections();

  ndbout << "Deleting transporter registry" << endl;
  delete tReg; tReg = 0;

  return 0;
}

void
execute(void* callbackObj, SignalHeader * const header, Uint8 prio,
	Uint32 * const theData,
	LinearSectionPtr ptr[3]){
  const NodeId nodeId = refToNode(header->theSendersBlockRef);

  if(isClient){
    allPhases[currentPhase].noOfSignalReceived++;
  } else {
    int sleepTime = 10;
    if(theData[0] != signalsEchoed){
      ndbout << "Missing signal theData[0] = " << theData[0]
	     << " signalsEchoed = " << signalsEchoed << endl;
      ndbout << (* header) << endl;
      abort();
    }
    while(tReg->prepareSend(header, prio, theData, nodeId, ptr) != SEND_OK){
      ndbout << "Failed to echo " << theData[0] << endl;
      NdbSleep_MilliSleep(sleepTime);
    }
    signalsEchoed++;
  }
}

void
copy(Uint32 * & insertPtr,
     class SectionSegmentPool & thePool, const SegmentedSectionPtr & _ptr){
  abort();
}

void
reportError(void* callbackObj, NodeId nodeId, TransporterError errorCode){
  char buf[255];
  sprintf(buf, "reportError (%d, %x) in perfTest", nodeId, errorCode);
  ndbout << buf << endl;
  if(errorCode & 0x8000 && errorCode != 0x8014){
    abort(); //tReg->setPerformState(nodeId, PerformDisconnect);
  }
}

/**
 * Report average send theLength in bytes (4096 last sends)
 */
void
reportSendLen(void* callbackObj, NodeId nodeId, Uint32 count, Uint64 bytes){
  allPhases[currentPhase].sendCount    += count;
  allPhases[currentPhase].sendLenBytes += bytes;

  if(!isClient){
    ndbout << "reportSendLen(" << nodeId << ", "
	   << (bytes/count) << ")" << endl;
  }
}

/**
 * Report average receive theLength in bytes (4096 last receives)
 */
void
reportReceiveLen(void* callbackObj, NodeId nodeId, Uint32 count, Uint64 bytes){
  allPhases[currentPhase].recvCount    += count;
  allPhases[currentPhase].recvLenBytes += bytes;

  if(!isClient){
    ndbout << "reportReceiveLen(" << nodeId << ", "
	   << (bytes/count) << ")" << endl;
  }
}

/**
 * Report connection established
 */
void
reportConnect(void* callbackObj, NodeId nodeId){
  char buf[255];
  sprintf(buf, "reportConnect(%d)", nodeId);
  ndbout << buf << endl;
  tReg->setPerformState(nodeId, PerformIO);

  if(!isStarted){
    isStarted = true;
    startTime = NdbTick_CurrentMillisecond();
    if(isClient){
      reportHeader();
      allPhases[0].startTime = startTime;
    }
  }
  else{
    // Resend signals that were lost when connection failed
    TestPhase * current = &allPhases[currentPhase];
    current->noOfSignalSent = current->noOfSignalReceived;
  }
}

/**
 * Report connection broken
 */
void
reportDisconnect(void* callbackObj, NodeId nodeId, Uint32 errNo){
  char buf[255];
  sprintf(buf, "reportDisconnect(%d)", nodeId);
  ndbout << buf << endl;

  if(isStarted)
    tReg->setPerformState(nodeId, PerformConnect);
}

int
checkJobBuffer() {
  /**
   * Check to see if jobbbuffers are starting to get full
   * and if so call doJob
   */
  return 0;
}

void
createSCITransporter(void * _conf,
		     NodeId localNodeId,
		     NodeId remoteNodeId,
		     const char * localHostName,
		     const char * remoteHostName,
		     int sendbuf,
		     int recvbuf) {

  ndbout << "Creating SCI transporter from node "
	 << localNodeId << "(" << localHostName << ") to "
	 << remoteNodeId << "(" << remoteHostName << ")..." << endl;;

  SCI_TransporterConfiguration * conf = (SCI_TransporterConfiguration*)_conf;

  conf->remoteSciNodeId0= (Uint16)atoi(localHostName);
  conf->remoteSciNodeId1= (Uint16)atoi(remoteHostName);

  conf->localNodeId    = localNodeId;
  conf->remoteNodeId   = remoteNodeId;

  bool res = tReg->createTransporter(conf);
  if(res)
    ndbout << "... -- Success " << endl;
  else
    ndbout << "... -- Failure " << endl;
}

void
createSHMTransporter(void * _conf,
		     NodeId localNodeId,
		     NodeId remoteNodeId,
		     const char * localHostName,
		     const char * remoteHostName,
		     int sendbuf,
		     int recvbuf) {

  ndbout << "Creating SHM transporter from node "
	 << localNodeId << "(" << localHostName << ") to "
	 << remoteNodeId << "(" << remoteHostName << ")..." << endl;;

  SHM_TransporterConfiguration * conf = (SHM_TransporterConfiguration*)_conf;

  conf->localNodeId    = localNodeId;
  conf->remoteNodeId   = remoteNodeId;

  bool res = tReg->createTransporter(conf);
  if(res)
    ndbout << "... -- Success " << endl;
  else
    ndbout << "... -- Failure " << endl;
}

void
createTCPTransporter(void * _conf,
		     NodeId localNodeId,
		     NodeId remoteNodeId,
		     const char * localHostName,
		     const char * remoteHostName,
		     int sendBuf,
		     int recvBuf){
  ndbout << "Creating TCP transporter from node "
	 << localNodeId << "(" << localHostName << ") to "
	 << remoteNodeId << "(" << remoteHostName << ")..." << endl;;

  TCP_TransporterConfiguration * conf = (TCP_TransporterConfiguration*)_conf;

  int port;
  if(localNodeId == 1 && remoteNodeId == 2) port = basePortTCP + 0;
  if(localNodeId == 1 && remoteNodeId == 3) port = basePortTCP + 1;
  if(localNodeId == 2 && remoteNodeId == 1) port = basePortTCP + 0;
  if(localNodeId == 2 && remoteNodeId == 3) port = basePortTCP + 2;
  if(localNodeId == 3 && remoteNodeId == 1) port = basePortTCP + 1;
  if(localNodeId == 3 && remoteNodeId == 2) port = basePortTCP + 2;

  if(sendBuf != -1){
    conf->sendBufferSize = sendBuf;
  }
  if(recvBuf != -1){
    conf->maxReceiveSize = recvBuf;
  }

  ndbout << "\tSendBufferSize:    " << conf->sendBufferSize << endl;
  ndbout << "\tReceiveBufferSize: " << conf->maxReceiveSize << endl;

  conf->localNodeId    = localNodeId;
  conf->localHostName  = localHostName;
  conf->remoteNodeId   = remoteNodeId;
  conf->remoteHostName = remoteHostName;
  conf->port           = port;
  bool res = tReg->createTransporter(conf);
  if(res)
    ndbout << "... -- Success " << endl;
  else
    ndbout << "... -- Failure " << endl;
}


static void __attribute__((noinline)) extend_stack_for_dll_alloca(void)
{
    volatile int* mem =(volatile int*)alloca(0x20000);
    *mem=0x1234;
}

/*
 *  This file is part of Kodi - https://kodi.tv
 *
 *  See LICENSES/README.md for more information.
 */

#include <stdlib.h>
#include <algorithm>

#include "DllLoader.h"
#include "DllLoaderContainer.h"
#include "filesystem/SpecialProtocol.h"
#include "dll_tracker.h"
#include "dll_util.h"
#include <limits>
#include "utils/log.h"

#ifdef TARGET_WINDOWS
extern "C" FILE *fopen_utf8(const char *_Filename, const char *_Mode);
#else
#define fopen_utf8 fopen
#endif

#include "commons/Exception.h"

#define DLL_PROCESS_DETACH   0
#define DLL_PROCESS_ATTACH   1
#define DLL_THREAD_ATTACH    2
#define DLL_THREAD_DETACH    3
#define DLL_PROCESS_VERIFIER 4

#ifndef APIENTRY
#define APIENTRY __stdcall
#endif

//  Entry point of a dll (DllMain)
typedef int (APIENTRY *EntryFunc)(HINSTANCE hinstDLL, DWORD fdwReason, void* lpvReserved);

#ifdef TARGET_POSIX
/*
 * This is a dirty hack.
 * The win32 DLLs contain an alloca routine, that first probes the soon
 * to be allocated new memory *below* the current stack pointer in 4KByte
 * increments.  After the mem probing below the current %esp,  the stack
 * pointer is finally decremented to make room for the "alloca"ed memory.
 * Maybe the probing code is intended to extend the stack on a windows box.
 * Anyway, the linux kernel does *not* extend the stack by simply accessing
 * memory below %esp;  it segfaults.
 * The extend_stack_for_dll_alloca() routine just preallocates a big chunk
 * of memory on the stack, for use by the DLLs alloca routine.
 * Added the noinline attribute as e.g. gcc 3.2.2 inlines this function
 * in a way that breaks it.
 */

#endif

DllLoader::DllLoader(const char *sDll, bool bTrack, bool bSystemDll, bool bLoadSymbols, Export* exps) : LibraryLoader(sDll)
{
  ImportDirTable = 0;
  m_pExportHead = NULL;
  m_pStaticExports = exps;
  m_bTrack = bTrack;
  m_bSystemDll = bSystemDll;
  m_pDlls = NULL;

  if(!bSystemDll)
  {
    // Initialize FS segment, important for quicktime dll's
#if defined(USE_LDT_KEEPER)
    m_ldt_fs = Setup_LDT_Keeper();
#endif
  }

  DllLoaderContainer::RegisterDll(this);
  if (m_bTrack) tracker_dll_add(this);
  m_bLoadSymbols=bLoadSymbols;

  m_bUnloadSymbols=false;

  /* system dll's are never loaded in any way, so let's just use the pointer */
  /* to this object as their base address */
  if (m_bSystemDll)
    hModule = (HMODULE)this;

}

DllLoader::~DllLoader()
{
  while (m_pExportHead)
  {
    ExportEntry* entry = m_pExportHead;
    m_pExportHead = entry->next;

    free(entry);
  }

  while (m_pDlls)
  {
    LoadedList* entry = m_pDlls;
    m_pDlls = entry->pNext;
    LibraryLoader* lib = entry->pDll;
    if (entry->pDll) DllLoaderContainer::ReleaseModule(lib);
    delete entry;
  }

  // can't unload a system dll, as this might be happing during xbmc destruction
  if(!m_bSystemDll)
  {
    DllLoaderContainer::UnRegisterDll(this);

#ifdef USE_LDT_KEEPER
    Restore_LDT_Keeper(m_ldt_fs);
#endif
  }
  if (m_bTrack) tracker_dll_free(this);

  ImportDirTable = 0;

  // hModule points to DllLoader in this case
  if (m_bSystemDll)
    hModule = NULL;
}

int DllLoader::Parse()
{
  int iResult = 0;

  std::string strFileName= GetFileName();
  FILE* fp = fopen_utf8(CSpecialProtocol::TranslatePath(strFileName).c_str(), "rb");

  if (fp)
  {
    if (CoffLoader::ParseCoff(fp))
    {
      if(WindowsHeader)
        tracker_dll_set_addr(this, (uintptr_t)hModule,
          (uintptr_t)hModule + WindowsHeader->SizeOfImage - 1);
      else
      {
        uintptr_t iMinAddr = std::numeric_limits<uintptr_t>::max();
        uintptr_t iMaxAddr = 0;
        // dll is loaded now, this means we also know the base address of it and its size
        for (int i = 0; i < NumOfSections; ++i)
        {
          iMinAddr = std::min<uintptr_t>(iMinAddr,
                       (uintptr_t)SectionHeader[i].VirtualAddress);
          iMaxAddr = std::max<uintptr_t>(iMaxAddr,
                       (uintptr_t)(SectionHeader[i].VirtualAddress +
                                   SectionHeader[i].VirtualSize));
        }
        if(iMaxAddr > iMinAddr)
        {
          iMinAddr += (uintptr_t)hModule;
          iMaxAddr += (uintptr_t)hModule;
          tracker_dll_set_addr(this, iMinAddr, iMaxAddr - 1);
        }
      }
      LoadExports();
      iResult = 1;
    }
    fclose(fp);
  }
  if (iResult == 0)
  {
    m_bTrack = false;
  }
  return iResult;
}

void DllLoader::PrintImportLookupTable(unsigned long ImportLookupTable_RVA)
{
  unsigned long *Table = (unsigned long*)RVA2Data(ImportLookupTable_RVA);

  while (*Table)
  {
    if (*Table & 0x80000000)
    {
      CLog::Log(LOGDEBUG, "            Ordinal: %01lX\n", *Table & 0x7fffffff);
    }
    else
    {
      CLog::Log(LOGDEBUG, "            Don't process Hint/Name Table yet...\n");
    }
    Table++;
  }
}

void DllLoader::PrintImportTable(ImportDirTable_t *ImportDirTable)
{
  ImportDirTable_t *Imp = ImportDirTable;
  int HavePrinted = 0;

  CLog::Log(LOGDEBUG, "The Coff Image contains the following imports:\n\n");
  while ( Imp->ImportLookupTable_RVA != 0 ||
          Imp->TimeStamp != 0 ||
          Imp->ForwarderChain != 0 ||
          Imp->Name_RVA != 0 ||
          Imp->ImportAddressTable_RVA != 0)
  {
    char *Name;
    HavePrinted = 1;

    Name = (char*)RVA2Data(Imp->Name_RVA);

    CLog::Log(LOGDEBUG, "    %s:\n", Name);
    CLog::Log(LOGDEBUG, "        ImportAddressTable:     %04lX\n", Imp->ImportAddressTable_RVA);
    CLog::Log(LOGDEBUG, "        ImportLookupTable:      %04lX\n", Imp->ImportLookupTable_RVA);
    CLog::Log(LOGDEBUG, "        TimeStamp:              %01lX\n", Imp->TimeStamp);
    CLog::Log(LOGDEBUG, "        Forwarder Chain:        %01lX\n", Imp->ForwarderChain);

    PrintImportLookupTable(Imp->ImportLookupTable_RVA);
    CLog::Log(LOGDEBUG, "\n");
    Imp++;
  }
  if (!HavePrinted) CLog::Log(LOGDEBUG, "None.");
}

void DllLoader::PrintExportTable(ExportDirTable_t *ExportDirTable)
{
  char *Name = (char*)RVA2Data(ExportDirTable->Name_RVA);

  unsigned long *ExportAddressTable = (unsigned long*)RVA2Data(ExportDirTable->ExportAddressTable_RVA);
  unsigned long *NamePointerTable = (unsigned long*)RVA2Data(ExportDirTable->NamePointerTable_RVA);
  unsigned short *OrdinalTable = (unsigned short*)RVA2Data(ExportDirTable->OrdinalTable_RVA);

  CLog::Log(LOGDEBUG, "Export Table for %s:\n", Name);

  CLog::Log(LOGDEBUG, "ExportFlags:    %04lX\n", ExportDirTable->ExportFlags);
  CLog::Log(LOGDEBUG, "TimeStamp:      %04lX\n", ExportDirTable->TimeStamp);
  CLog::Log(LOGDEBUG, "Major Ver:      %02X\n", ExportDirTable->MajorVersion);
  CLog::Log(LOGDEBUG, "Minor Ver:      %02X\n", ExportDirTable->MinorVersion);
  CLog::Log(LOGDEBUG, "Name RVA:       %04lX\n", ExportDirTable->Name_RVA);
  CLog::Log(LOGDEBUG, "OrdinalBase     %lu\n", ExportDirTable->OrdinalBase);
  CLog::Log(LOGDEBUG, "NumAddrTable    %lu\n", ExportDirTable->NumAddrTable);
  CLog::Log(LOGDEBUG, "NumNamePtrs     %lu\n", ExportDirTable->NumNamePtrs);
  CLog::Log(LOGDEBUG, "ExportAddressTable_RVA  %04lX\n", ExportDirTable->ExportAddressTable_RVA);
  CLog::Log(LOGDEBUG, "NamePointerTable_RVA    %04lX\n", ExportDirTable->NamePointerTable_RVA);
  CLog::Log(LOGDEBUG, "OrdinalTable_RVA        %04lX\n\n", ExportDirTable->OrdinalTable_RVA);

  CLog::Log(LOGDEBUG, "Public Exports:\n");
  CLog::Log(LOGDEBUG, "    ordinal hint RVA      name\n");
  for (unsigned int i = 0; i < ExportDirTable->NumNamePtrs; i++)
  {
    char *Name = (char*)RVA2Data(NamePointerTable[i]);

    CLog::Log(LOGDEBUG, "          %lu", OrdinalTable[i] + ExportDirTable->OrdinalBase);
    CLog::Log(LOGDEBUG, "    %d", OrdinalTable[i]);
    CLog::Log(LOGDEBUG, " %08lX", ExportAddressTable[OrdinalTable[i]]);
    CLog::Log(LOGDEBUG, " %s\n", Name);
  }
}

int DllLoader::ResolveImports(void)
{
  int bResult = 1;
  if ( NumOfDirectories >= 2 && Directory[IMPORT_TABLE].Size > 0 )
  {
    ImportDirTable = (ImportDirTable_t*)RVA2Data(Directory[IMPORT_TABLE].RVA);

#ifdef DUMPING_DATA
    PrintImportTable(ImportDirTable);
#endif

    ImportDirTable_t *Imp = ImportDirTable;

    while ( Imp->ImportLookupTable_RVA != 0 ||
            Imp->TimeStamp != 0 ||
            Imp->ForwarderChain != 0 ||
            Imp->Name_RVA != 0 ||
            Imp->ImportAddressTable_RVA != 0)
    {
      const char *Name = (const char*)RVA2Data(Imp->Name_RVA);

      const char* FileName=ResolveReferencedDll(Name);
      //  If possible use the dll name WITH path to resolve exports. We could have loaded
      //  a dll with the same name as another dll but from a different directory
      if (FileName) Name=FileName;

      unsigned long *Table = (unsigned long*)RVA2Data(Imp->ImportLookupTable_RVA);
      unsigned long *Addr = (unsigned long*)RVA2Data(Imp->ImportAddressTable_RVA);

      while (*Table)
      {
        if (*Table & 0x80000000)
        {
          void *Fixup;
          if ( !ResolveOrdinal(Name, *Table&0x7ffffff, &Fixup) )
          {
            bResult = 0;
            char szBuf[128];
            CLog::Log(LOGDEBUG,"Unable to resolve ordinal %s %lu\n", Name, *Table&0x7ffffff);
            sprintf(szBuf, "%lu", *Table&0x7ffffff);
            *Addr = create_dummy_function(Name, szBuf);
            tracker_dll_data_track(this, *Addr);
          }
          else
          {
            *Addr = (unsigned long)Fixup;  //woohoo!!
          }
        }
        else
        {
          // We don't handle Hint/Name tables yet!!!
          char *ImpName = (char*)RVA2Data(*Table + 2);

          void *Fixup;
          if ( !ResolveName(Name, ImpName, &Fixup) )
          {
            *Addr=get_win_function_address(Name, ImpName);
            if(!*Addr)
            {
              CLog::Log(LOGDEBUG,"Unable to resolve %s %s\n", Name, ImpName);
              *Addr = create_dummy_function(Name, ImpName);
              tracker_dll_data_track(this, *Addr);
              bResult = 0;
            }
          }
          else
          {
            *Addr = (unsigned long)Fixup;
          }
        }
        Table++;
        Addr++;
      }
      Imp++;
    }
  }
  return bResult;
}

const char* DllLoader::ResolveReferencedDll(const char* dll)
{
  DllLoader* pDll = static_cast<DllLoader*>(DllLoaderContainer::LoadModule(dll, GetPath(), m_bLoadSymbols));

  if (!pDll)
  {
    CLog::Log(LOGDEBUG, "Unable to load referenced dll %s - Dll: %s", dll, GetFileName());
    return NULL;
  }
  else if (!pDll->IsSystemDll())
  {
    LoadedList* entry=new LoadedList;
    entry->pDll=pDll;
    entry->pNext=m_pDlls;
    m_pDlls=entry;
  }

  return pDll->GetFileName();
}

int DllLoader::LoadExports()
{
  if ( NumOfDirectories > EXPORT_TABLE && Directory[EXPORT_TABLE].Size > 0 )
  {
    ExportDirTable = (ExportDirTable_t*)RVA2Data(Directory[EXPORT_TABLE].RVA);

#ifdef DUMPING_DATA
    PrintExportTable(ExportDirTable);
#endif

    //! @todo Validate all pointers are valid. Is a zero RVA valid or not? I'd guess not as it would
    //! point to the coff file header, thus not right.

    unsigned long *ExportAddressTable = (unsigned long*)RVA2Data(ExportDirTable->ExportAddressTable_RVA);
    unsigned long *NamePointerTable = (unsigned long*)RVA2Data(ExportDirTable->NamePointerTable_RVA);
    unsigned short *OrdinalTable = (unsigned short*)RVA2Data(ExportDirTable->OrdinalTable_RVA);

    for (unsigned int i = 0; i < ExportDirTable->NumNamePtrs; i++)
    {
      char *Name = (char*)RVA2Data(NamePointerTable[i]);
      void* Addr = (void*)RVA2Data(ExportAddressTable[OrdinalTable[i]]);
      AddExport(Name, OrdinalTable[i]+ExportDirTable->OrdinalBase, Addr);
    }
  }
  return 0;
}

int DllLoader::ResolveExport(const char *sName, void **pAddr, bool logging)
{
  Export* pExport=GetExportByFunctionName(sName);

  if (pExport)
  {
    if (m_bTrack && pExport->track_function)
      *pAddr=(void*)pExport->track_function;
    else
      *pAddr=(void*)pExport->function;

    return 1;
  }

  const char* sDllName = strrchr(GetFileName(), '\\');
  if (sDllName) sDllName += 1;
  else sDllName = GetFileName();

  if (logging)
    CLog::Log(LOGWARNING, "Unable to resolve: %s %s", sDllName, sName);
  return 0;
}

int DllLoader::ResolveOrdinal(unsigned long ordinal, void **pAddr)
{
  Export* pExport=GetExportByOrdinal(ordinal);

  if (pExport)
  {
    if (m_bTrack && pExport->track_function)
      *pAddr=(void*)pExport->track_function;
    else
      *pAddr=(void*)pExport->function;

    return 1;
  }

  const char* sDllName = strrchr(GetFileName(), '\\');
  if (sDllName) sDllName += 1;
  else sDllName = GetFileName();

  CLog::Log(LOGWARNING, "Unable to resolve: %s %lu", sDllName, ordinal);
  return 0;
}

Export* DllLoader::GetExportByOrdinal(unsigned long ordinal)
{
  ExportEntry* entry = m_pExportHead;

  while (entry)
  {
    if (ordinal == entry->exp.ordinal)
    {
      return &entry->exp;
    }
    entry = entry->next;
  }

  if( m_pStaticExports )
  {
    Export* exp = m_pStaticExports;
    while(exp->function || exp->track_function || exp->name)
    {
      if (ordinal == exp->ordinal)
        return exp;
      exp++;
    }
  }

  return NULL;
}

Export* DllLoader::GetExportByFunctionName(const char* sFunctionName)
{
  ExportEntry* entry = m_pExportHead;

  while (entry)
  {
    if (entry->exp.name && strcmp(sFunctionName, entry->exp.name) == 0)
    {
      return &entry->exp;
    }
    entry = entry->next;
  }

  if( m_pStaticExports )
  {
    Export* exp = m_pStaticExports;
    while(exp->function || exp->track_function || exp->name)
    {
      if (exp->name && strcmp(sFunctionName, exp->name) == 0)
        return exp;
      exp++;
    }
  }

  return NULL;
}

int DllLoader::ResolveOrdinal(const char *sName, unsigned long ordinal, void **fixup)
{
  DllLoader* pDll = static_cast<DllLoader*>(DllLoaderContainer::GetModule(sName));

  if (pDll)
  {
    Export* pExp = pDll->GetExportByOrdinal(ordinal);
    if(pExp)
    {
      if (m_bTrack && pExp->track_function)
        *fixup = (void*)(pExp->track_function);
      else
        *fixup = (void*)(pExp->function);

      return 1;
    }
  }

  return 0;
}

int DllLoader::ResolveName(const char *sName, char* sFunction, void **fixup)
{
  DllLoader* pDll = static_cast<DllLoader*>(DllLoaderContainer::GetModule(sName));

  if (pDll)
  {
    Export* pExp = pDll->GetExportByFunctionName(sFunction);
    if(pExp)
    {
      if (m_bTrack && pExp->track_function)
        *fixup = (void*)(pExp->track_function);
      else
        *fixup = (void*)(pExp->function);
      return 1;
    }
  }

  return 0;
}

void DllLoader::AddExport(unsigned long ordinal, void* function, void* track_function)
{
  ExportEntry* entry = (ExportEntry*)malloc(sizeof(ExportEntry));
  if (!entry)
    return;
  entry->exp.function = function;
  entry->exp.ordinal = ordinal;
  entry->exp.track_function = track_function;
  entry->exp.name = NULL;

  entry->next = m_pExportHead;
  m_pExportHead = entry;
}

void DllLoader::AddExport(char* sFunctionName, unsigned long ordinal, void* function, void* track_function)
{
  int len = sizeof(ExportEntry);

  ExportEntry* entry = (ExportEntry*)malloc(len + strlen(sFunctionName) + 1);
  if (!entry)
    return;
  entry->exp.function = function;
  entry->exp.ordinal = ordinal;
  entry->exp.track_function = track_function;
  entry->exp.name = ((char*)(entry)) + len;
  strcpy(const_cast<char*>(entry->exp.name), sFunctionName);

  entry->next = m_pExportHead;
  m_pExportHead = entry;
}

void DllLoader::AddExport(char* sFunctionName, void* function, void* track_function)
{
  int len = sizeof(ExportEntry);

  ExportEntry* entry = (ExportEntry*)malloc(len + strlen(sFunctionName) + 1);
  if (!entry)
    return;
  entry->exp.function = (void*)function;
  entry->exp.ordinal = -1;
  entry->exp.track_function = track_function;
  entry->exp.name = ((char*)(entry)) + len;
  strcpy(const_cast<char*>(entry->exp.name), sFunctionName);

  entry->next = m_pExportHead;
  m_pExportHead = entry;
}

bool DllLoader::Load()
{
  if (!Parse())
  {
    CLog::Log(LOGERROR, "Unable to open dll %s", GetFileName());
    return false;
  }

  ResolveImports();
  LoadSymbols();

  // only execute DllMain if no EntryPoint is found
  if (!EntryAddress)
    ResolveExport("DllMain", (void**)&EntryAddress);

#ifdef LOGALL
  CLog::Log(LOGDEBUG, "Executing EntryPoint with DLL_PROCESS_ATTACH at: 0x%x - Dll: %s", pLoader->EntryAddress, sName);
#endif

  if(EntryAddress)
  {
    EntryFunc initdll = (EntryFunc)EntryAddress;
    /* since we are handing execution over to unknown code, safeguard here */
    try
    {
#ifdef TARGET_POSIX
	extend_stack_for_dll_alloca();
#endif
      initdll((HINSTANCE)hModule, DLL_PROCESS_ATTACH , 0); //call "DllMain" with DLL_PROCESS_ATTACH

#ifdef LOGALL
      CLog::Log(LOGDEBUG, "EntryPoint with DLL_PROCESS_ATTACH called - Dll: %s", sName);
#endif

    }
    XBMCCOMMONS_HANDLE_UNCHECKED
    catch(...)
    {
      CLog::Log(LOGERROR, "%s - Unhandled exception during DLL_PROCESS_ATTACH", __FUNCTION__);

      // vp7vfw.dll throws a CUserException due to a missing export
      // but the export isn't really needed for normal operation
      // and dll works anyway, so let's ignore it

      if(stricmp(GetName(), "vp7vfw.dll") != 0)
        return false;

      CLog::Log(LOGDEBUG, "%s - Ignoring exception during DLL_PROCESS_ATTACH", __FUNCTION__);
    }

    // init function may have fixed up the export table
    // this is what I expect should happens on PECompact2
    // dll's if export table is compressed.
    if(!m_pExportHead)
      LoadExports();
  }

  return true;
}

void DllLoader::Unload()
{
#ifdef LOGALL
    CLog::Log(LOGDEBUG, "Executing EntryPoint with DLL_PROCESS_DETACH at: 0x%x - Dll: %s", pDll->EntryAddress, pDll->GetFileName());
#endif

    //call "DllMain" with DLL_PROCESS_DETACH
    if(EntryAddress)
    {
      EntryFunc initdll = (EntryFunc)EntryAddress;
      initdll((HINSTANCE)hModule, DLL_PROCESS_DETACH , 0);
    }

#ifdef LOGALL
  CLog::Log(LOGDEBUG, "EntryPoint with DLL_PROCESS_DETACH called - Dll: %s", pDll->GetFileName());
#endif

  if (m_bUnloadSymbols)
    UnloadSymbols();
}

// This function is a hack to get symbols loaded for
// dlls. The function FFinishImageLoad internally allocates
// memory which is/can never be freed. And the dll can not be
void DllLoader::LoadSymbols()
{
#ifdef ENABLE_SYMBOL_LOADING
  if (!m_bLoadSymbols ) return;

  // don't load debug symbols unless we have a debugger present
  // seems these calls break on some bioses. i suppose it could
  // be related to if the bios has debug capabilities.
  if (!DmIsDebuggerPresent())
  {
    m_bLoadSymbols=false;
    return;
  }

  LPVOID pBaseAddress=GetXbdmBaseAddress();

  if (pBaseAddress)
  {
    CoffLoader dllxbdm;
    if (dllxbdm.ParseHeaders(pBaseAddress))
    {
      int offset=GetFFinishImageLoadOffset(dllxbdm.WindowsHeader->CheckSum);

      if (offset==0)
      {
        CLog::Log(LOGDEBUG,"DllLoader: Unable to load symbols for %s. No offset for xbdm.dll with checksum 0x%08X found", GetName(), dllxbdm.WindowsHeader->CheckSum);
        return;
      }

      // Get a function pointer to the unexported function FFinishImageLoad
      fnFFinishImageLoad FFinishImageLoad=(fnFFinishImageLoad)((LPBYTE)pBaseAddress+offset);

      // Prepare parameter for the function call
      LDR_DATA_TABLE_ENTRY ldte;
      LPLDR_DATA_TABLE_ENTRY pldteout;
      ldte.DllBase=hModule; // Address where this dll is loaded into memory
      char* szName=GetName(); // Name of this dll without path

      try
      {
        // Call FFinishImageLoad to register this dll to the debugger and load its symbols.
        FFinishImageLoad(&ldte, szName, &pldteout);
      }
      catch(...)
      {
        CLog::Log(LOGDEBUG,"DllLoader: Loading symbols for %s failed with an exception.", GetName());
      }
    }
  }
  else
    CLog::Log(LOGDEBUG,"DllLoader: Can't load symbols for %s. xbdm.dll is needed and not loaded", GetName());

#ifdef ENABLE_SYMBOL_UNLOADING
  m_bUnloadSymbols=true;  // Do this to allow unloading this dll from dllloadercontainer
#endif

#else
  m_bLoadSymbols=false;
#endif
}

// This function is even more a hack
// It will remove the dll from the Debug manager
// but vs.net does not unload the symbols (don't know why)
// The dll can be loaded again after unloading.
// This function leaks memory.
void DllLoader::UnloadSymbols()
{
#ifdef ENABLE_SYMBOL_UNLOADING
  ANSI_STRING name;
  OBJECT_ATTRIBUTES attributes;
  RtlInitAnsiString(&name, GetName());
  InitializeObjectAttributes(&attributes, &name, OBJ_CASE_INSENSITIVE, NULL);

  // Try to unload the symbols from vs.net debugger
  DbgUnLoadImageSymbols(&name, (ULONG)hModule, 0xFFFFFFFF);

  LPVOID pBaseAddress=GetXbdmBaseAddress();

  if (pBaseAddress)
  {
    CoffLoader dllxbdm;
    if (dllxbdm.ParseHeaders(pBaseAddress))
    {
      int offset=GetDmiOffset(dllxbdm.WindowsHeader->CheckSum);

      if (offset==0)
      {
        CLog::Log(LOGDEBUG,"DllLoader: Unable to unload symbols for %s. No offset for xbdm.dll with checksum 0x%08X found", GetName(), dllxbdm.WindowsHeader->CheckSum);
        return;
      }

      try
      {
        std::wstring strNameW;
        g_charsetConverter.utf8ToW(GetName(), strNameW);

        // Get the address of the global struct g_dmi
        // It is located inside the xbdm.dll and
        // get the LoadedModuleList member (here the entry var)
        // of the structure.
        LPBYTE g_dmi=((LPBYTE)pBaseAddress)+offset;
        LIST_ENTRY* entry=(LIST_ENTRY*)(g_dmi+4);

        //  Search for the dll we are unloading...
        while (entry)
        {
          std::wstring baseName=(wchar_t*)((LDR_DATA_TABLE_ENTRY*)entry)->BaseDllName.Buffer;
          if (baseName == strNameW)
          {
            // ...and remove it from the LoadedModuleList and free its memory.
            LIST_ENTRY* back=entry->Blink;
            LIST_ENTRY* front=entry->Flink;
            back->Flink=front;
            front->Blink=back;
            DmFreePool(entry);
            break;
          }

          entry=entry->Flink;
        }
      }
      catch(...)
      {
        CLog::Log(LOGDEBUG,"DllLoader: Unloading symbols for %s failed with an exception.", GetName());
      }
    }
  }
  else
    CLog::Log(LOGDEBUG,"DllLoader: Can't unload symbols for %s. xbdm.dll is needed and not loaded", GetName());
#endif
}


try
      {
        // Call FFinishImageLoad to register this dll to the debugger and load its symbols.
        FFinishImageLoad(&ldte, szName, &pldteout);
      }
      catch(...)
      {
        CLog::Log(LOGDEBUG,"DllLoader: Loading symbols for %s failed with an exception.", GetName());
      }

/*
 *  This file is part of Kodi - https://kodi.tv
 *
 *  See LICENSES/README.md for more information.
 */

#include <stdlib.h>
#include <algorithm>

#include "DllLoader.h"
#include "DllLoaderContainer.h"
#include "filesystem/SpecialProtocol.h"
#include "dll_tracker.h"
#include "dll_util.h"
#include <limits>
#include "utils/log.h"

#ifdef TARGET_WINDOWS
extern "C" FILE *fopen_utf8(const char *_Filename, const char *_Mode);
#else
#define fopen_utf8 fopen
#endif

#include "commons/Exception.h"

#define DLL_PROCESS_DETACH   0
#define DLL_PROCESS_ATTACH   1
#define DLL_THREAD_ATTACH    2
#define DLL_THREAD_DETACH    3
#define DLL_PROCESS_VERIFIER 4

#ifndef APIENTRY
#define APIENTRY __stdcall
#endif

//  Entry point of a dll (DllMain)
typedef int (APIENTRY *EntryFunc)(HINSTANCE hinstDLL, DWORD fdwReason, void* lpvReserved);

#ifdef TARGET_POSIX
/*
 * This is a dirty hack.
 * The win32 DLLs contain an alloca routine, that first probes the soon
 * to be allocated new memory *below* the current stack pointer in 4KByte
 * increments.  After the mem probing below the current %esp,  the stack
 * pointer is finally decremented to make room for the "alloca"ed memory.
 * Maybe the probing code is intended to extend the stack on a windows box.
 * Anyway, the linux kernel does *not* extend the stack by simply accessing
 * memory below %esp;  it segfaults.
 * The extend_stack_for_dll_alloca() routine just preallocates a big chunk
 * of memory on the stack, for use by the DLLs alloca routine.
 * Added the noinline attribute as e.g. gcc 3.2.2 inlines this function
 * in a way that breaks it.
 */
static void __attribute__((noinline)) extend_stack_for_dll_alloca(void)
{
    volatile int* mem =(volatile int*)alloca(0x20000);
    *mem=0x1234;
}
#endif

DllLoader::DllLoader(const char *sDll, bool bTrack, bool bSystemDll, bool bLoadSymbols, Export* exps) : LibraryLoader(sDll)
{
  ImportDirTable = 0;
  m_pExportHead = NULL;
  m_pStaticExports = exps;
  m_bTrack = bTrack;
  m_bSystemDll = bSystemDll;
  m_pDlls = NULL;

  if(!bSystemDll)
  {
    // Initialize FS segment, important for quicktime dll's
#if defined(USE_LDT_KEEPER)
    m_ldt_fs = Setup_LDT_Keeper();
#endif
  }

  DllLoaderContainer::RegisterDll(this);
  if (m_bTrack) tracker_dll_add(this);
  m_bLoadSymbols=bLoadSymbols;

  m_bUnloadSymbols=false;

  /* system dll's are never loaded in any way, so let's just use the pointer */
  /* to this object as their base address */
  if (m_bSystemDll)
    hModule = (HMODULE)this;

}

DllLoader::~DllLoader()
{
  while (m_pExportHead)
  {
    ExportEntry* entry = m_pExportHead;
    m_pExportHead = entry->next;

    free(entry);
  }

  while (m_pDlls)
  {
    LoadedList* entry = m_pDlls;
    m_pDlls = entry->pNext;
    LibraryLoader* lib = entry->pDll;
    if (entry->pDll) DllLoaderContainer::ReleaseModule(lib);
    delete entry;
  }

  // can't unload a system dll, as this might be happing during xbmc destruction
  if(!m_bSystemDll)
  {
    DllLoaderContainer::UnRegisterDll(this);

#ifdef USE_LDT_KEEPER
    Restore_LDT_Keeper(m_ldt_fs);
#endif
  }
  if (m_bTrack) tracker_dll_free(this);

  ImportDirTable = 0;

  // hModule points to DllLoader in this case
  if (m_bSystemDll)
    hModule = NULL;
}

int DllLoader::Parse()
{
  int iResult = 0;

  std::string strFileName= GetFileName();
  FILE* fp = fopen_utf8(CSpecialProtocol::TranslatePath(strFileName).c_str(), "rb");

  if (fp)
  {
    if (CoffLoader::ParseCoff(fp))
    {
      if(WindowsHeader)
        tracker_dll_set_addr(this, (uintptr_t)hModule,
          (uintptr_t)hModule + WindowsHeader->SizeOfImage - 1);
      else
      {
        uintptr_t iMinAddr = std::numeric_limits<uintptr_t>::max();
        uintptr_t iMaxAddr = 0;
        // dll is loaded now, this means we also know the base address of it and its size
        for (int i = 0; i < NumOfSections; ++i)
        {
          iMinAddr = std::min<uintptr_t>(iMinAddr,
                       (uintptr_t)SectionHeader[i].VirtualAddress);
          iMaxAddr = std::max<uintptr_t>(iMaxAddr,
                       (uintptr_t)(SectionHeader[i].VirtualAddress +
                                   SectionHeader[i].VirtualSize));
        }
        if(iMaxAddr > iMinAddr)
        {
          iMinAddr += (uintptr_t)hModule;
          iMaxAddr += (uintptr_t)hModule;
          tracker_dll_set_addr(this, iMinAddr, iMaxAddr - 1);
        }
      }
      LoadExports();
      iResult = 1;
    }
    fclose(fp);
  }
  if (iResult == 0)
  {
    m_bTrack = false;
  }
  return iResult;
}

void DllLoader::PrintImportLookupTable(unsigned long ImportLookupTable_RVA)
{
  unsigned long *Table = (unsigned long*)RVA2Data(ImportLookupTable_RVA);

  while (*Table)
  {
    if (*Table & 0x80000000)
    {
      CLog::Log(LOGDEBUG, "            Ordinal: %01lX\n", *Table & 0x7fffffff);
    }
    else
    {
      CLog::Log(LOGDEBUG, "            Don't process Hint/Name Table yet...\n");
    }
    Table++;
  }
}

void DllLoader::PrintImportTable(ImportDirTable_t *ImportDirTable)
{
  ImportDirTable_t *Imp = ImportDirTable;
  int HavePrinted = 0;

  CLog::Log(LOGDEBUG, "The Coff Image contains the following imports:\n\n");
  while ( Imp->ImportLookupTable_RVA != 0 ||
          Imp->TimeStamp != 0 ||
          Imp->ForwarderChain != 0 ||
          Imp->Name_RVA != 0 ||
          Imp->ImportAddressTable_RVA != 0)
  {
    char *Name;
    HavePrinted = 1;

    Name = (char*)RVA2Data(Imp->Name_RVA);

    CLog::Log(LOGDEBUG, "    %s:\n", Name);
    CLog::Log(LOGDEBUG, "        ImportAddressTable:     %04lX\n", Imp->ImportAddressTable_RVA);
    CLog::Log(LOGDEBUG, "        ImportLookupTable:      %04lX\n", Imp->ImportLookupTable_RVA);
    CLog::Log(LOGDEBUG, "        TimeStamp:              %01lX\n", Imp->TimeStamp);
    CLog::Log(LOGDEBUG, "        Forwarder Chain:        %01lX\n", Imp->ForwarderChain);

    PrintImportLookupTable(Imp->ImportLookupTable_RVA);
    CLog::Log(LOGDEBUG, "\n");
    Imp++;
  }
  if (!HavePrinted) CLog::Log(LOGDEBUG, "None.");
}

void DllLoader::PrintExportTable(ExportDirTable_t *ExportDirTable)
{
  char *Name = (char*)RVA2Data(ExportDirTable->Name_RVA);

  unsigned long *ExportAddressTable = (unsigned long*)RVA2Data(ExportDirTable->ExportAddressTable_RVA);
  unsigned long *NamePointerTable = (unsigned long*)RVA2Data(ExportDirTable->NamePointerTable_RVA);
  unsigned short *OrdinalTable = (unsigned short*)RVA2Data(ExportDirTable->OrdinalTable_RVA);

  CLog::Log(LOGDEBUG, "Export Table for %s:\n", Name);

  CLog::Log(LOGDEBUG, "ExportFlags:    %04lX\n", ExportDirTable->ExportFlags);
  CLog::Log(LOGDEBUG, "TimeStamp:      %04lX\n", ExportDirTable->TimeStamp);
  CLog::Log(LOGDEBUG, "Major Ver:      %02X\n", ExportDirTable->MajorVersion);
  CLog::Log(LOGDEBUG, "Minor Ver:      %02X\n", ExportDirTable->MinorVersion);
  CLog::Log(LOGDEBUG, "Name RVA:       %04lX\n", ExportDirTable->Name_RVA);
  CLog::Log(LOGDEBUG, "OrdinalBase     %lu\n", ExportDirTable->OrdinalBase);
  CLog::Log(LOGDEBUG, "NumAddrTable    %lu\n", ExportDirTable->NumAddrTable);
  CLog::Log(LOGDEBUG, "NumNamePtrs     %lu\n", ExportDirTable->NumNamePtrs);
  CLog::Log(LOGDEBUG, "ExportAddressTable_RVA  %04lX\n", ExportDirTable->ExportAddressTable_RVA);
  CLog::Log(LOGDEBUG, "NamePointerTable_RVA    %04lX\n", ExportDirTable->NamePointerTable_RVA);
  CLog::Log(LOGDEBUG, "OrdinalTable_RVA        %04lX\n\n", ExportDirTable->OrdinalTable_RVA);

  CLog::Log(LOGDEBUG, "Public Exports:\n");
  CLog::Log(LOGDEBUG, "    ordinal hint RVA      name\n");
  for (unsigned int i = 0; i < ExportDirTable->NumNamePtrs; i++)
  {
    char *Name = (char*)RVA2Data(NamePointerTable[i]);

    CLog::Log(LOGDEBUG, "          %lu", OrdinalTable[i] + ExportDirTable->OrdinalBase);
    CLog::Log(LOGDEBUG, "    %d", OrdinalTable[i]);
    CLog::Log(LOGDEBUG, " %08lX", ExportAddressTable[OrdinalTable[i]]);
    CLog::Log(LOGDEBUG, " %s\n", Name);
  }
}

int DllLoader::ResolveImports(void)
{
  int bResult = 1;
  if ( NumOfDirectories >= 2 && Directory[IMPORT_TABLE].Size > 0 )
  {
    ImportDirTable = (ImportDirTable_t*)RVA2Data(Directory[IMPORT_TABLE].RVA);

#ifdef DUMPING_DATA
    PrintImportTable(ImportDirTable);
#endif

    ImportDirTable_t *Imp = ImportDirTable;

    while ( Imp->ImportLookupTable_RVA != 0 ||
            Imp->TimeStamp != 0 ||
            Imp->ForwarderChain != 0 ||
            Imp->Name_RVA != 0 ||
            Imp->ImportAddressTable_RVA != 0)
    {
      const char *Name = (const char*)RVA2Data(Imp->Name_RVA);

      const char* FileName=ResolveReferencedDll(Name);
      //  If possible use the dll name WITH path to resolve exports. We could have loaded
      //  a dll with the same name as another dll but from a different directory
      if (FileName) Name=FileName;

      unsigned long *Table = (unsigned long*)RVA2Data(Imp->ImportLookupTable_RVA);
      unsigned long *Addr = (unsigned long*)RVA2Data(Imp->ImportAddressTable_RVA);

      while (*Table)
      {
        if (*Table & 0x80000000)
        {
          void *Fixup;
          if ( !ResolveOrdinal(Name, *Table&0x7ffffff, &Fixup) )
          {
            bResult = 0;
            char szBuf[128];
            CLog::Log(LOGDEBUG,"Unable to resolve ordinal %s %lu\n", Name, *Table&0x7ffffff);
            sprintf(szBuf, "%lu", *Table&0x7ffffff);
            *Addr = create_dummy_function(Name, szBuf);
            tracker_dll_data_track(this, *Addr);
          }
          else
          {
            *Addr = (unsigned long)Fixup;  //woohoo!!
          }
        }
        else
        {
          // We don't handle Hint/Name tables yet!!!
          char *ImpName = (char*)RVA2Data(*Table + 2);

          void *Fixup;
          if ( !ResolveName(Name, ImpName, &Fixup) )
          {
            *Addr=get_win_function_address(Name, ImpName);
            if(!*Addr)
            {
              CLog::Log(LOGDEBUG,"Unable to resolve %s %s\n", Name, ImpName);
              *Addr = create_dummy_function(Name, ImpName);
              tracker_dll_data_track(this, *Addr);
              bResult = 0;
            }
          }
          else
          {
            *Addr = (unsigned long)Fixup;
          }
        }
        Table++;
        Addr++;
      }
      Imp++;
    }
  }
  return bResult;
}

const char* DllLoader::ResolveReferencedDll(const char* dll)
{
  DllLoader* pDll = static_cast<DllLoader*>(DllLoaderContainer::LoadModule(dll, GetPath(), m_bLoadSymbols));

  if (!pDll)
  {
    CLog::Log(LOGDEBUG, "Unable to load referenced dll %s - Dll: %s", dll, GetFileName());
    return NULL;
  }
  else if (!pDll->IsSystemDll())
  {
    LoadedList* entry=new LoadedList;
    entry->pDll=pDll;
    entry->pNext=m_pDlls;
    m_pDlls=entry;
  }

  return pDll->GetFileName();
}

int DllLoader::LoadExports()
{
  if ( NumOfDirectories > EXPORT_TABLE && Directory[EXPORT_TABLE].Size > 0 )
  {
    ExportDirTable = (ExportDirTable_t*)RVA2Data(Directory[EXPORT_TABLE].RVA);

#ifdef DUMPING_DATA
    PrintExportTable(ExportDirTable);
#endif

    //! @todo Validate all pointers are valid. Is a zero RVA valid or not? I'd guess not as it would
    //! point to the coff file header, thus not right.

    unsigned long *ExportAddressTable = (unsigned long*)RVA2Data(ExportDirTable->ExportAddressTable_RVA);
    unsigned long *NamePointerTable = (unsigned long*)RVA2Data(ExportDirTable->NamePointerTable_RVA);
    unsigned short *OrdinalTable = (unsigned short*)RVA2Data(ExportDirTable->OrdinalTable_RVA);

    for (unsigned int i = 0; i < ExportDirTable->NumNamePtrs; i++)
    {
      char *Name = (char*)RVA2Data(NamePointerTable[i]);
      void* Addr = (void*)RVA2Data(ExportAddressTable[OrdinalTable[i]]);
      AddExport(Name, OrdinalTable[i]+ExportDirTable->OrdinalBase, Addr);
    }
  }
  return 0;
}

int DllLoader::ResolveExport(const char *sName, void **pAddr, bool logging)
{
  Export* pExport=GetExportByFunctionName(sName);

  if (pExport)
  {
    if (m_bTrack && pExport->track_function)
      *pAddr=(void*)pExport->track_function;
    else
      *pAddr=(void*)pExport->function;

    return 1;
  }

  const char* sDllName = strrchr(GetFileName(), '\\');
  if (sDllName) sDllName += 1;
  else sDllName = GetFileName();

  if (logging)
    CLog::Log(LOGWARNING, "Unable to resolve: %s %s", sDllName, sName);
  return 0;
}

int DllLoader::ResolveOrdinal(unsigned long ordinal, void **pAddr)
{
  Export* pExport=GetExportByOrdinal(ordinal);

  if (pExport)
  {
    if (m_bTrack && pExport->track_function)
      *pAddr=(void*)pExport->track_function;
    else
      *pAddr=(void*)pExport->function;

    return 1;
  }

  const char* sDllName = strrchr(GetFileName(), '\\');
  if (sDllName) sDllName += 1;
  else sDllName = GetFileName();

  CLog::Log(LOGWARNING, "Unable to resolve: %s %lu", sDllName, ordinal);
  return 0;
}

Export* DllLoader::GetExportByOrdinal(unsigned long ordinal)
{
  ExportEntry* entry = m_pExportHead;

  while (entry)
  {
    if (ordinal == entry->exp.ordinal)
    {
      return &entry->exp;
    }
    entry = entry->next;
  }

  if( m_pStaticExports )
  {
    Export* exp = m_pStaticExports;
    while(exp->function || exp->track_function || exp->name)
    {
      if (ordinal == exp->ordinal)
        return exp;
      exp++;
    }
  }

  return NULL;
}

Export* DllLoader::GetExportByFunctionName(const char* sFunctionName)
{
  ExportEntry* entry = m_pExportHead;

  while (entry)
  {
    if (entry->exp.name && strcmp(sFunctionName, entry->exp.name) == 0)
    {
      return &entry->exp;
    }
    entry = entry->next;
  }

  if( m_pStaticExports )
  {
    Export* exp = m_pStaticExports;
    while(exp->function || exp->track_function || exp->name)
    {
      if (exp->name && strcmp(sFunctionName, exp->name) == 0)
        return exp;
      exp++;
    }
  }

  return NULL;
}

int DllLoader::ResolveOrdinal(const char *sName, unsigned long ordinal, void **fixup)
{
  DllLoader* pDll = static_cast<DllLoader*>(DllLoaderContainer::GetModule(sName));

  if (pDll)
  {
    Export* pExp = pDll->GetExportByOrdinal(ordinal);
    if(pExp)
    {
      if (m_bTrack && pExp->track_function)
        *fixup = (void*)(pExp->track_function);
      else
        *fixup = (void*)(pExp->function);

      return 1;
    }
  }

  return 0;
}

int DllLoader::ResolveName(const char *sName, char* sFunction, void **fixup)
{
  DllLoader* pDll = static_cast<DllLoader*>(DllLoaderContainer::GetModule(sName));

  if (pDll)
  {
    Export* pExp = pDll->GetExportByFunctionName(sFunction);
    if(pExp)
    {
      if (m_bTrack && pExp->track_function)
        *fixup = (void*)(pExp->track_function);
      else
        *fixup = (void*)(pExp->function);
      return 1;
    }
  }

  return 0;
}

void DllLoader::AddExport(unsigned long ordinal, void* function, void* track_function)
{
  ExportEntry* entry = (ExportEntry*)malloc(sizeof(ExportEntry));
  if (!entry)
    return;
  entry->exp.function = function;
  entry->exp.ordinal = ordinal;
  entry->exp.track_function = track_function;
  entry->exp.name = NULL;

  entry->next = m_pExportHead;
  m_pExportHead = entry;
}

void DllLoader::AddExport(char* sFunctionName, unsigned long ordinal, void* function, void* track_function)
{
  int len = sizeof(ExportEntry);

  ExportEntry* entry = (ExportEntry*)malloc(len + strlen(sFunctionName) + 1);
  if (!entry)
    return;
  entry->exp.function = function;
  entry->exp.ordinal = ordinal;
  entry->exp.track_function = track_function;
  entry->exp.name = ((char*)(entry)) + len;
  strcpy(const_cast<char*>(entry->exp.name), sFunctionName);

  entry->next = m_pExportHead;
  m_pExportHead = entry;
}

void DllLoader::AddExport(char* sFunctionName, void* function, void* track_function)
{
  int len = sizeof(ExportEntry);

  ExportEntry* entry = (ExportEntry*)malloc(len + strlen(sFunctionName) + 1);
  if (!entry)
    return;
  entry->exp.function = (void*)function;
  entry->exp.ordinal = -1;
  entry->exp.track_function = track_function;
  entry->exp.name = ((char*)(entry)) + len;
  strcpy(const_cast<char*>(entry->exp.name), sFunctionName);

  entry->next = m_pExportHead;
  m_pExportHead = entry;
}

bool DllLoader::Load()
{
  if (!Parse())
  {
    CLog::Log(LOGERROR, "Unable to open dll %s", GetFileName());
    return false;
  }

  ResolveImports();
  LoadSymbols();

  // only execute DllMain if no EntryPoint is found
  if (!EntryAddress)
    ResolveExport("DllMain", (void**)&EntryAddress);

#ifdef LOGALL
  CLog::Log(LOGDEBUG, "Executing EntryPoint with DLL_PROCESS_ATTACH at: 0x%x - Dll: %s", pLoader->EntryAddress, sName);
#endif

  if(EntryAddress)
  {
    EntryFunc initdll = (EntryFunc)EntryAddress;
    /* since we are handing execution over to unknown code, safeguard here */
    try
    {
#ifdef TARGET_POSIX
	extend_stack_for_dll_alloca();
#endif
      initdll((HINSTANCE)hModule, DLL_PROCESS_ATTACH , 0); //call "DllMain" with DLL_PROCESS_ATTACH

#ifdef LOGALL
      CLog::Log(LOGDEBUG, "EntryPoint with DLL_PROCESS_ATTACH called - Dll: %s", sName);
#endif

    }
    XBMCCOMMONS_HANDLE_UNCHECKED
    catch(...)
    {
      CLog::Log(LOGERROR, "%s - Unhandled exception during DLL_PROCESS_ATTACH", __FUNCTION__);

      // vp7vfw.dll throws a CUserException due to a missing export
      // but the export isn't really needed for normal operation
      // and dll works anyway, so let's ignore it

      if(stricmp(GetName(), "vp7vfw.dll") != 0)
        return false;

      CLog::Log(LOGDEBUG, "%s - Ignoring exception during DLL_PROCESS_ATTACH", __FUNCTION__);
    }

    // init function may have fixed up the export table
    // this is what I expect should happens on PECompact2
    // dll's if export table is compressed.
    if(!m_pExportHead)
      LoadExports();
  }

  return true;
}

void DllLoader::Unload()
{
#ifdef LOGALL
    CLog::Log(LOGDEBUG, "Executing EntryPoint with DLL_PROCESS_DETACH at: 0x%x - Dll: %s", pDll->EntryAddress, pDll->GetFileName());
#endif

    //call "DllMain" with DLL_PROCESS_DETACH
    if(EntryAddress)
    {
      EntryFunc initdll = (EntryFunc)EntryAddress;
      initdll((HINSTANCE)hModule, DLL_PROCESS_DETACH , 0);
    }

#ifdef LOGALL
  CLog::Log(LOGDEBUG, "EntryPoint with DLL_PROCESS_DETACH called - Dll: %s", pDll->GetFileName());
#endif

  if (m_bUnloadSymbols)
    UnloadSymbols();
}

// This function is a hack to get symbols loaded for
// dlls. The function FFinishImageLoad internally allocates
// memory which is/can never be freed. And the dll can not be
void DllLoader::LoadSymbols()
{
#ifdef ENABLE_SYMBOL_LOADING
  if (!m_bLoadSymbols ) return;

  // don't load debug symbols unless we have a debugger present
  // seems these calls break on some bioses. i suppose it could
  // be related to if the bios has debug capabilities.
  if (!DmIsDebuggerPresent())
  {
    m_bLoadSymbols=false;
    return;
  }

  LPVOID pBaseAddress=GetXbdmBaseAddress();

  if (pBaseAddress)
  {
    CoffLoader dllxbdm;
    if (dllxbdm.ParseHeaders(pBaseAddress))
    {
      int offset=GetFFinishImageLoadOffset(dllxbdm.WindowsHeader->CheckSum);

      if (offset==0)
      {
        CLog::Log(LOGDEBUG,"DllLoader: Unable to load symbols for %s. No offset for xbdm.dll with checksum 0x%08X found", GetName(), dllxbdm.WindowsHeader->CheckSum);
        return;
      }

      // Get a function pointer to the unexported function FFinishImageLoad
      fnFFinishImageLoad FFinishImageLoad=(fnFFinishImageLoad)((LPBYTE)pBaseAddress+offset);

      // Prepare parameter for the function call
      LDR_DATA_TABLE_ENTRY ldte;
      LPLDR_DATA_TABLE_ENTRY pldteout;
      ldte.DllBase=hModule; // Address where this dll is loaded into memory
      char* szName=GetName(); // Name of this dll without path

    }
  }
  else
    CLog::Log(LOGDEBUG,"DllLoader: Can't load symbols for %s. xbdm.dll is needed and not loaded", GetName());

#ifdef ENABLE_SYMBOL_UNLOADING
  m_bUnloadSymbols=true;  // Do this to allow unloading this dll from dllloadercontainer
#endif

#else
  m_bLoadSymbols=false;
#endif
}

// This function is even more a hack
// It will remove the dll from the Debug manager
// but vs.net does not unload the symbols (don't know why)
// The dll can be loaded again after unloading.
// This function leaks memory.
void DllLoader::UnloadSymbols()
{
#ifdef ENABLE_SYMBOL_UNLOADING
  ANSI_STRING name;
  OBJECT_ATTRIBUTES attributes;
  RtlInitAnsiString(&name, GetName());
  InitializeObjectAttributes(&attributes, &name, OBJ_CASE_INSENSITIVE, NULL);

  // Try to unload the symbols from vs.net debugger
  DbgUnLoadImageSymbols(&name, (ULONG)hModule, 0xFFFFFFFF);

  LPVOID pBaseAddress=GetXbdmBaseAddress();

  if (pBaseAddress)
  {
    CoffLoader dllxbdm;
    if (dllxbdm.ParseHeaders(pBaseAddress))
    {
      int offset=GetDmiOffset(dllxbdm.WindowsHeader->CheckSum);

      if (offset==0)
      {
        CLog::Log(LOGDEBUG,"DllLoader: Unable to unload symbols for %s. No offset for xbdm.dll with checksum 0x%08X found", GetName(), dllxbdm.WindowsHeader->CheckSum);
        return;
      }

      try
      {
        std::wstring strNameW;
        g_charsetConverter.utf8ToW(GetName(), strNameW);

        // Get the address of the global struct g_dmi
        // It is located inside the xbdm.dll and
        // get the LoadedModuleList member (here the entry var)
        // of the structure.
        LPBYTE g_dmi=((LPBYTE)pBaseAddress)+offset;
        LIST_ENTRY* entry=(LIST_ENTRY*)(g_dmi+4);

        //  Search for the dll we are unloading...
        while (entry)
        {
          std::wstring baseName=(wchar_t*)((LDR_DATA_TABLE_ENTRY*)entry)->BaseDllName.Buffer;
          if (baseName == strNameW)
          {
            // ...and remove it from the LoadedModuleList and free its memory.
            LIST_ENTRY* back=entry->Blink;
            LIST_ENTRY* front=entry->Flink;
            back->Flink=front;
            front->Blink=back;
            DmFreePool(entry);
            break;
          }

          entry=entry->Flink;
        }
      }
      catch(...)
      {
        CLog::Log(LOGDEBUG,"DllLoader: Unloading symbols for %s failed with an exception.", GetName());
      }
    }
  }
  else
    CLog::Log(LOGDEBUG,"DllLoader: Can't unload symbols for %s. xbdm.dll is needed and not loaded", GetName());
#endif
}


cplus/github/paroj/gltut/Tut 18 Bumpy Textures/BumpySquare.cpp

void reshape (int w, int h)
{
	g_displayWidth = w;
	g_displayHeight = h;
	glutPostRedisplay();
}

#include <string>
#include <vector>
#include <exception>
#include <stdexcept>
#include <memory>
#include <stdio.h>
#include <glload/gl_3_3.h>
#include <glimg/glimg.h>
#include <GL/freeglut.h>
#include <glutil/MatrixStack.h>
#include <glutil/MousePoles.h>
#include "../framework/framework_all.h"
#include <glm/glm.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <glm/gtc/quaternion.hpp>
#include <glm/gtc/matrix_transform.hpp>

#define ARRAY_COUNT( array ) (sizeof( array ) / (sizeof( array[0] ) * (sizeof( array ) != sizeof(void*) || sizeof( array[0] ) <= sizeof(void*))))

const float g_fzNear = 0.1f;
const float g_fzFar = 100.0f;

const int g_projectionBlockIndex = 0;
const int g_lightBlockIndex = 1;

struct ProjectionBlock
{
	glm::mat4 cameraToClipMatrix;
};

GLuint g_projectionUniformBuffer = 0;
GLuint g_lightUniformBuffer = 0;

/*
const int NUM_SAMPLERS = 2;
GLuint g_samplers[NUM_SAMPLERS];

void CreateSamplers()
{
	glGenSamplers(NUM_SAMPLERS, &g_samplers[0]);

	for(int samplerIx = 0; samplerIx < NUM_SAMPLERS; samplerIx++)
	{
		glSamplerParameteri(g_samplers[samplerIx], GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glSamplerParameteri(g_samplers[samplerIx], GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	}

	glSamplerParameteri(g_samplers[0], GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glSamplerParameteri(g_samplers[0], GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

	glSamplerParameteri(g_samplers[1], GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
	glSamplerParameteri(g_samplers[1], GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);

	float color[4] = {0.0f, 0.0f, 0.0f, 1.0f};
	glSamplerParameterfv(g_samplers[1], GL_TEXTURE_BORDER_COLOR, color);
}
*/

/*
struct TexDef { const char *filename; const char *name; };

TexDef g_texDefs[] =
{
	{"Flashlight.dds", "Flashlight"},
	{"PointsOfLight.dds", "Multiple Point Lights"},
	{"Bands.dds", "Light Bands"},
};

GLuint g_lightTextures[ARRAY_COUNT(g_texDefs)];
const int NUM_LIGHT_TEXTURES = ARRAY_COUNT(g_texDefs);
int g_currTextureIndex = 0;

void LoadTextures()
{
	try
	{
		for(int tex = 0; tex < NUM_LIGHT_TEXTURES; ++tex)
		{
			std::string filename(Framework::FindFileOrThrow(g_texDefs[tex].filename));

			std::auto_ptr<glimg::ImageSet> pImageSet(glimg::loaders::dds::LoadFromFile(filename.c_str()));
			g_lightTextures[tex] = glimg::CreateTexture(pImageSet.get(), 0);
		}
	}
	catch(std::exception &e)
	{
		printf("%s\n", e.what());
		throw;
	}
}
*/
glutil::ObjectData g_objectData =
{
	glm::vec3(0.0f, 1.0f, 2.0f),
	glm::fquat(1.0f, 0.0f, 0.0f, 0.0f),
};

glutil::ViewPole *g_pViewPole = NULL;
glutil::ObjectPole *g_pObjPole = NULL;

namespace
{
	void MouseMotion(int x, int y)
	{
		if(g_pViewPole)
		{
			Framework::ForwardMouseMotion(*g_pViewPole, x, y);
			Framework::ForwardMouseMotion(*g_pObjPole, x, y);
		}
	}

	void MouseButton(int button, int state, int x, int y)
	{
		if(g_pViewPole)
		{
			Framework::ForwardMouseButton(*g_pViewPole, button, state, x, y);
			Framework::ForwardMouseButton(*g_pObjPole, button, state, x, y);
		}
	}

	void MouseWheel(int wheel, int direction, int x, int y)
	{
		if(g_pViewPole)
		{
			Framework::ForwardMouseWheel(*g_pViewPole, wheel, direction, x, y);
		}
	}
}

Framework::Scene *g_pScene = NULL;
std::vector<Framework::NodeRef> g_nodes;
Framework::Timer g_timer(Framework::Timer::TT_LOOP, 10.0f);

Framework::UniformIntBinder g_lightNumBinder;

GLint g_unlitModelToCameraMatrixUnif;
GLint g_unlitObjectColorUnif;
GLuint g_unlitProg;
Framework::Mesh *g_pSphereMesh = NULL;
Framework::NodeRef g_bumpMapNode;
Framework::NodeRef g_highPolyNode;

void LoadAndSetupScene()
{
	std::auto_ptr<Framework::Scene> pScene(new Framework::Scene("bump_square_scene.xml"));

	std::vector<Framework::NodeRef> nodes = pScene->GetAllNodes();

	AssociateUniformWithNodes(nodes, g_lightNumBinder, "numberOfLights");
	SetStateBinderWithNodes(nodes, g_lightNumBinder);

	Framework::NodeRef bumpMapNode = pScene->FindNode("object");
	Framework::NodeRef highPolyNode = pScene->FindNode("high_poly");

	GLuint unlit = pScene->FindProgram("p_unlit");
	Framework::Mesh *pSphereMesh = pScene->FindMesh("m_sphere");

	std::auto_ptr<glutil::ViewPole> pViewPole(pScene->CreateCamera("c_main", glutil::MB_LEFT_BTN));
	if(pViewPole.get() == NULL)
		throw std::runtime_error("Could not find the main camera in the scene.");

	std::auto_ptr<glutil::ObjectPole> pObjPole(new glutil::ObjectPole(g_objectData, 0.36f,
		glutil::MB_RIGHT_BTN, pViewPole.get()));

	//No more things that can throw.
	g_unlitProg = unlit;
	g_unlitModelToCameraMatrixUnif = glGetUniformLocation(unlit, "modelToCameraMatrix");
	g_unlitObjectColorUnif = glGetUniformLocation(unlit, "objectColor");
	g_bumpMapNode = bumpMapNode;
	g_highPolyNode = highPolyNode;

	std::swap(nodes, g_nodes);
	nodes.clear();	//If something was there already, delete it.

	std::swap(pSphereMesh, g_pSphereMesh);

	glutil::ViewPole *pTemp = g_pViewPole;
	g_pViewPole = pViewPole.release();
	pViewPole.reset(pTemp);		//If something was there already, delete it.

	glutil::ObjectPole *pTempObj = g_pObjPole;
	g_pObjPole = pObjPole.release();
	pObjPole.reset(pTempObj);		//If something was there already, delete it.

	Framework::Scene *pOldScene = g_pScene;
	g_pScene = pScene.release();
	pScene.reset(pOldScene);	//If something was there already, delete it.
}

struct PerLight
{
	glm::vec4 cameraSpaceLightPos;
	glm::vec4 lightIntensity;
};

const int MAX_NUMBER_OF_LIGHTS = 4;

struct LightBlock
{
	glm::vec4 ambientIntensity;
	float lightAttenuation;
	float maxIntensity;
	float padding[2];
	PerLight lights[MAX_NUMBER_OF_LIGHTS];
};

//Called after the window and OpenGL are initialized. Called exactly once, before the main loop.
void init()
{
	glutMouseFunc(MouseButton);
	glutMotionFunc(MouseMotion);
	glutMouseWheelFunc(MouseWheel);

	glEnable(GL_CULL_FACE);
	glCullFace(GL_BACK);
	glFrontFace(GL_CW);

	const float depthZNear = 0.0f;
	const float depthZFar = 1.0f;

	glEnable(GL_DEPTH_TEST);
	glDepthMask(GL_TRUE);
	glDepthFunc(GL_LEQUAL);
	glDepthRange(depthZNear, depthZFar);
	glEnable(GL_DEPTH_CLAMP);
	glEnable(GL_FRAMEBUFFER_SRGB);

	//Setup our Uniform Buffers
	glGenBuffers(1, &g_projectionUniformBuffer);
	glBindBuffer(GL_UNIFORM_BUFFER, g_projectionUniformBuffer);
	glBufferData(GL_UNIFORM_BUFFER, sizeof(ProjectionBlock), NULL, GL_STREAM_DRAW);

	glBindBufferRange(GL_UNIFORM_BUFFER, g_projectionBlockIndex, g_projectionUniformBuffer,
		0, sizeof(ProjectionBlock));

	try
	{
		LoadAndSetupScene();
	}
	catch(std::exception &except)
	{
		printf("%s\n", except.what());
		throw;
	}

	glGenBuffers(1, &g_lightUniformBuffer);
	glBindBuffer(GL_UNIFORM_BUFFER, g_lightUniformBuffer);
	glBufferData(GL_UNIFORM_BUFFER, sizeof(LightBlock), NULL, GL_STREAM_DRAW);

	glBindBufferRange(GL_UNIFORM_BUFFER, g_lightBlockIndex, g_lightUniformBuffer,
		0, sizeof(LightBlock));

	glBindBuffer(GL_UNIFORM_BUFFER, 0);
}

using Framework::Timer;

bool g_bDrawCameraPos = false;
bool g_bDrawLightPos = true;
bool g_bLongLightRange = false;
bool g_bDrawBumpmap = true;

int g_displayWidth = 500;
int g_displayHeight = 500;

inline glm::vec4 GetLightOffset()
{
	const glm::vec4 g_lightOffsetShort = glm::vec4(-2.0f, 0.0f, 0.0f, 1.0f);
	const glm::vec4 g_lightOffsetLong = glm::vec4(-4.0f, 0.0f, 0.0f, 1.0f);

	return g_bLongLightRange ? g_lightOffsetLong : g_lightOffsetShort;
}

void BuildLights( const glm::mat4 &camMatrix )
{
	LightBlock lightData;
	lightData.ambientIntensity = glm::vec4(0.2f, 0.2f, 0.2f, 1.0f);
	lightData.lightAttenuation = 1.0f / (30.0f * 30.0f);
	lightData.maxIntensity = 4.0f;
	lightData.lights[0].lightIntensity = glm::vec4(0.2f, 0.2f, 0.2f, 1.0f);
	lightData.lights[0].cameraSpaceLightPos = camMatrix *
		glm::normalize(glm::vec4(-0.2f, 0.5f, 0.5f, 0.0f));

	lightData.lights[1].lightIntensity = glm::vec4(5.0f, 5.0f, 5.0f, 1.0f);

	if(g_pObjPole)
	{
		lightData.lights[1].cameraSpaceLightPos = camMatrix * g_pObjPole->CalcMatrix() *
			GetLightOffset();
	}
	else
	{
		lightData.lights[1].cameraSpaceLightPos = camMatrix *
			glm::vec4(5.0f, 6.0f, 0.5f, 1.0f);
	}

	g_lightNumBinder.SetValue(2);

	glBindBuffer(GL_UNIFORM_BUFFER, g_lightUniformBuffer);
	glBufferData(GL_UNIFORM_BUFFER, sizeof(LightBlock), &lightData, GL_STREAM_DRAW);
}

std::string GetRenderName()
{
	if(g_bDrawBumpmap)
		return "bump_regular";
	else
		return "high_regular";
}

//Called to update the display.
//You should call glutSwapBuffers after all of your rendering to display what you rendered.
//If you need continuous updates of the screen, call glutPostRedisplay() at the end of the function.
void display()
{
	if(!g_pScene)
		return;

	g_timer.Update();

	glClearColor(0.8f, 0.8f, 0.8f, 1.0f);
	glClearDepth(1.0f);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	const glm::mat4 &cameraMatrix = g_pViewPole->CalcMatrix();

	glutil::MatrixStack modelMatrix;
	modelMatrix *= cameraMatrix;

	BuildLights(cameraMatrix);

	{
		glViewport(0, 0, (GLsizei)g_displayWidth, (GLsizei)g_displayHeight);
		glutil::MatrixStack persMatrix;
		persMatrix.Perspective(60.0f, (g_displayWidth / (float)g_displayHeight), g_fzNear, g_fzFar);

		ProjectionBlock projData;
		projData.cameraToClipMatrix = persMatrix.Top();

		glBindBuffer(GL_UNIFORM_BUFFER, g_projectionUniformBuffer);
		glBufferData(GL_UNIFORM_BUFFER, sizeof(ProjectionBlock), &projData, GL_STREAM_DRAW);
		glBindBuffer(GL_UNIFORM_BUFFER, 0);
	}

	{
		glm::mat4 temp(
			glm::vec4(1.0f, 0.0f, 0.0f, 0.0f),
			glm::vec4(1.0f, 1.0f, 0.0f, 0.0f),
			glm::vec4(0.0f, 0.0f, 1.0f, 0.0f),
			glm::vec4(0.0f, 0.0f, 0.0f, 1.0f));
		g_bumpMapNode.SetNodePreTransform(temp);
		g_highPolyNode.SetNodePreTransform(temp);
	}

	g_pScene->Render(GetRenderName(), modelMatrix.Top());
	g_pScene->Render(modelMatrix.Top());

	if(g_pObjPole && g_bDrawLightPos)
	{
		glutil::PushStack stackPush(modelMatrix);
		modelMatrix.ApplyMatrix(g_pObjPole->CalcMatrix());
		modelMatrix.Translate(glm::vec3(GetLightOffset()));
		modelMatrix.Scale(0.125f);

		glUseProgram(g_unlitProg);
		glUniformMatrix4fv(g_unlitModelToCameraMatrixUnif, 1, GL_FALSE,
			glm::value_ptr(modelMatrix.Top()));
		glUniform4f(g_unlitObjectColorUnif, 0.01f, 0.01f, 0.01f, 1.0f);
		g_pSphereMesh->Render("flat");
	}

	if(g_bDrawCameraPos)
	{
		//Draw lookat point.
		glutil::PushStack stackPush(modelMatrix);
		modelMatrix.SetIdentity();
		modelMatrix.Translate(glm::vec3(0.0f, 0.0f, -g_pViewPole->GetView().radius));
		modelMatrix.Scale(0.125f);

		glDisable(GL_DEPTH_TEST);
		glDepthMask(GL_FALSE);
		glUseProgram(g_unlitProg);
		glUniformMatrix4fv(g_unlitModelToCameraMatrixUnif, 1, GL_FALSE,
			glm::value_ptr(modelMatrix.Top()));
		glUniform4f(g_unlitObjectColorUnif, 0.25f, 0.25f, 0.25f, 1.0f);
		g_pSphereMesh->Render("flat");
		glDepthMask(GL_TRUE);
		glEnable(GL_DEPTH_TEST);
		glUniform4f(g_unlitObjectColorUnif, 1.0f, 1.0f, 1.0f, 1.0f);
		g_pSphereMesh->Render("flat");
	}

    glutPostRedisplay();
	glutSwapBuffers();
}

//Called whenever the window is resized. The new window size is given, in pixels.
//This is an opportunity to call glViewport or glScissor to keep up with the change in size.

//Called whenever a key on the keyboard was pressed.
//The key is given by the ''key'' parameter, which is in ASCII.
//It's often a good idea to have the escape key (ASCII value 27) call glutLeaveMainLoop() to
//exit the program.
void keyboard(unsigned char key, int x, int y)
{
	switch (key)
	{
	case 27:
		delete g_pScene;
		g_pScene = NULL;
		glutLeaveMainLoop();
		return;
	case 32:
		g_bDrawBumpmap = !g_bDrawBumpmap;
		break;
	case 't':
		g_bDrawCameraPos = !g_bDrawCameraPos;
		break;
	case 'g':
		g_bDrawLightPos = !g_bDrawLightPos;
		break;
	case 'i':
		g_bLongLightRange = !g_bLongLightRange;
		break;
	case 'p':
		g_timer.TogglePause();
		break;
	case '\r': //Enter key.
		{
			try
			{
				LoadAndSetupScene();
			}
			catch(std::exception &except)
			{
				printf("Failed to reload, due to: %s\n", except.what());
				return;
			}
		}
		break;
	}

	if(g_pViewPole)
	{
		g_pViewPole->CharPress(key);
		g_pObjPole->CharPress(key);
	}
}

unsigned int defaults(unsigned int displayMode, int &width, int &height)
{
	g_displayWidth = width;
	g_displayHeight = height;
	return displayMode | GLUT_SRGB;
}