/*
    ------------------------------------------------------------------

    This file is part of the Open Ephys GUI
    Copyright (C) 2012 Open Ephys

    ------------------------------------------------------------------

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

*/

#include "FPGAThread.h"
#include "../SourceNode.h"

#include <string.h>

FPGAThread::FPGAThread(SourceNode* sn) : DataThread(sn),
			isTransmitting(false),
			numchannels(32),
			deviceFound(false),
            ttlOutputVal(0),
            bytesToRead(20000),
            bufferWasAligned(false),
            ttlState(0)

{

	
	//const char* bitfilename = "./pipetest.bit";
#if JUCE_LINUX
	const char* bitfilename = "./pipetest.bit";
    const char* libname = "./libokFrontPanel64.so";
#endif
#if JUCE_WIN32
	const char* bitfilename = "pipetest.bit";
    const char* libname = NULL;
#endif
#if JUCE_MAC
    const char* bitfilename = "/Users/Josh/Programming/open-ephys/GUI/Resources/DLLs/pipetest.bit";
    const char* libname = "/Users/Josh/Programming/open-ephys/GUI/Resources/DLLs/libokFrontPanel.dylib";
#endif
    

	if (!okFrontPanelDLL_LoadLib(libname)) {
		printf("FrontPanel DLL could not be loaded.\n");
	}
	
	okFrontPanelDLL_GetVersion(dll_date, dll_time);
	//printf("FrontPanel DLL loaded.  Built: %s  %s\n", dll_date, dll_time);

	dev = new okCFrontPanel;

	strncpy(bitfile, bitfilename, 128);

	// Initialize the FPGA with our configuration bitfile.
	deviceFound = initializeFPGA(true);

	if (!deviceFound) {
		printf("FPGA could not be initialized.\n");
	} else {
		printf("FPGA interface initialized.\n");
	}

	Ndatabytes = numchannels*3;
	
	dataBuffer = new DataBuffer(numchannels, 10000);

	eventCode = 0;

}


FPGAThread::~FPGAThread() {
	
	std::cout << "FPGA interface destroyed." << std::endl;

	deleteAndZero(dev);

}

int FPGAThread::getNumChannels()
{
	return numchannels;
}

int FPGAThread::getNumEventChannels()
{
    return 16; // 8 inputs, 8 outputs
}

float FPGAThread::getSampleRate()
{
	return 28344.67;//12520.0;
}

float FPGAThread::getBitVolts()
{
	return 0.1907;
}

bool FPGAThread::foundInputSource()
{

	return true;

	// if (deviceFound)
	// {
	// 	if (okCFrontPanel::NoError != dev->ConfigureFPGA(bitfile)) 
	// 	{
	// 		printf("FPGA configuration failed.\n");
	// 		deviceFound = false;
	// 		return false;
	// 	}

	// } else {

	// 	// if (!initializeFPGA(false))
	// 	// {
	// 	// 	return false;
	// 	// } else {
	// 	// 	deviceFound = true;
	// 	// }

	// }

}

bool FPGAThread::startAcquisition()
{

   
   //alignBuffer(200);
   //alignBuffer(200);
   //alignBuffer(200);

  // alignBuffer();

  // alignBuffer();

	bufferWasAligned = false;

   startThread();

   isTransmitting = true;
   accumulator = 0;

   return true;
}

bool FPGAThread::stopAcquisition()
{

	isTransmitting = false;

	if (isThreadRunning()) {

        signalThreadShouldExit();
    }


    return true;
}

int FPGAThread::alignBuffer(int nBytes)
{
	long return_code;

	return_code = dev->ReadFromPipeOut(0xA0, nBytes, pBuffer);

	//std::cout << "Bytes read: " << return_code << std::endl;

	int j = 0;


	while (j < nBytes)
	{
        
		// look for timecode block (6 bytes)
		if (  (pBuffer[j] & 1) 
				&& (pBuffer[j+1] & 1) 
				&& (pBuffer[j+2] & 1) 
				&& (pBuffer[j+3] & 1) 
				&& (pBuffer[j+4] & 1) 
				&& (pBuffer[j+5] & 1) )
				//&& (j+5+Ndatabytes <= bytesToRead)   ) // indicated by last bit being 1
		{ 
			int numNeeded = j;

			std::cout << j << " ";

			return_code = dev->ReadFromPipeOut(0xA0, numNeeded, pBuffer);
			//std::cout << "First sample is " << j << std::endl;
			//std::cout << "Samples needed:  " << numNeeded << std::endl;
			break;
		}

		j++;	
	}

	return j;

}

bool FPGAThread::updateBuffer() 
{

	long return_code;
	
	if (!bufferWasAligned)
	{
		alignBuffer(100000);
		alignBuffer(2000);
		//return_code = dev->ReadFromPipeOut(0xA0, 206, pBuffer);
		//alignBuffer(2000);
		//alignBuffer(200);
		bufferWasAligned = true;
	}
	
	return_code = dev->ReadFromPipeOut(0xA0, bytesToRead, pBuffer);

    //std::cout << return_code << std::endl; should return number of bytes read [sizeof(pBuffer)]
    
	if (return_code == 0)
		return false;

    int j = 0;

    // coding scheme:
	// the code works on a per-byte level where each byte ends in a 0 for data bytes
	// or in 1 for timecode bytes. This is some overhead but makes data integrity checks 
	// pretty trivial.
	// 
	// headstages are A,B,C,D and another one for the breakout box T for the 0-5v TTL input
	// A1 is stage A channel 1 etc
    // ...............
    // tc     ttttttt1
    // tc     ttttttt1    (6*7bit timecode gives 42 bit gives 4.3980e+12 samples max
    // tc     ttttttt1     which should be about 7 years at 30KHz)
    // tc     ttttttt1
    // tc     ttttttt1
    // tc     ttttttt1
    // ttl_in xxxxxxxx
    // ttl_o  xxxxxxxx
    // A1  Hi xxxxxxx0
    // A1  Lo xxxxxxx0    (last bits 0)
    // A1  ch cccccYY0
    // B1  Hi xxxxxxx0    (YY are the two missing bits from A1, cccc is a 5bit ch code 1-32, or maybe checksum?)
    // B1  Lo xxxxxxx0
    // B1  ch cccccYY0
    // A2  Hi ........
    // ... remaining channel data ...
    // B32 ch cccccYY0
    //
    // ... next sample ...
    //
    

    int i = 0;
   // int samplesUsed = 0;
   // int startSample = 0;
    
    // new strategy: read in 201 bytes & find the first sample

    int firstSample;
    
	while (j < bytesToRead)
	{
        
		// look for timecode block (6 bytes)
		if (  (pBuffer[j] & 1) 
				&& (pBuffer[j+1] & 1) 
				&& (pBuffer[j+2] & 1) 
				&& (pBuffer[j+3] & 1) 
				&& (pBuffer[j+4] & 1) 
				&& (pBuffer[j+5] & 1) 
				&& (j+5+Ndatabytes <= bytesToRead)   ) // indicated by last bit being 1
		{ //read 6 bytes, assemble to 6*7 = 42 bits,  arranged in 6 bytes
			
			//std::cout << j << std::endl;
            
            i++;
            
            if (j % 200 != 0)
            {
            	std::cout << "Buffer not aligned " << j << " " << accumulator << std::endl;
            	//return false;
            }

            if (i == 1)
            {
                firstSample = j;
               
               //             "     Bytes read: " << bytesToRead << std::endl;
            }
            
			unsigned char timecode[6]; // 1st byte throw out last bit of each byte and just concatenate the other bytes in ascending order
			timecode[0] = (pBuffer[j] >> 1) | ((pBuffer[j+1] >> 1) << 7); // 2nd byte
			timecode[1] = (pBuffer[j+1] >> 2) | ((pBuffer[j+2] >> 1) << 6); // 3rd byte
			timecode[2] = (pBuffer[j+2] >> 3) | ((pBuffer[j+3] >> 1) << 5); // 4th byte
			timecode[3] = (pBuffer[j+3] >> 4) | ((pBuffer[j+4] >> 1) << 4); // 5th byte
			timecode[4] = (pBuffer[j+4] >> 5) | ((pBuffer[j+5] >> 1) << 3); // 6th byte
			timecode[5] = (pBuffer[j+5] >> 6);
            
            timestamp = (uint64(timecode[5]) << 35) +
                        (uint64(timecode[4]) << 28) +
                        (uint64(timecode[3]) << 32) +
                        (uint64(timecode[2]) << 16) +
                        (uint64(timecode[1]) << 8) +
                        (uint64(timecode[0]));
            
            
            
            eventCode = pBuffer[j+6]; // TTL input
            ttl_out = pBuffer[j+7];

            if (ttl_out > 0)
            {
            	eventCode |= 0x100;   // TTL output
            	//std::cout << "TLL out!" << std::endl;
            }
            	
		
			j += 8; //move cursor to 1st data byte

			// loop through sample data and condense from 3 bytes to 2 bytes
			uint16 hi; uint16 lo;

            // only take data from the first headstage (i.e., skip every other channel)
			for (int n = 0;  n < numchannels*2 ; n++)
			{
                
                if (n % 2 == 0)
                {
                
                    // last bit of first 2 is zero, replace with bits 1 and 2 from 3rd byte
                    hi = (pBuffer[j])    | (((  pBuffer[j+2]  >> 2) & ~(1<<6)) & ~(1<<7)) ;
                    lo = (pBuffer[j+1])  | (((  pBuffer[j+2]  >> 1) & ~(1<<1)) & ~(1<<7)) ;

                    uint16 samp = ((hi << 8) + lo);

                    thisSample[n/2] = -float(samp) * 0.1907f + 3000.0f; //- 6175.0f;
                }
                
                j += 3;


			}
            
            j -= 1; // step back in time

			dataBuffer->addToBuffer(thisSample, &timestamp, &eventCode, 1);
            
           // samplesUsed += 200;
            
		}
		
		j++; // keep scanning for timecodes
	}
    
   // if (startSample != 0 && bytesToRead > 10000)
    //    bytesToRead -= 2;
    //else
     //   bytesToRead = 20000;
    
    
    // - startSample - 199;// + (200-startSample) - 1;// + startSample +1;
    
    //overflowSize = sizeof(pBuffer) - samplesUsed;
    
//    if (overflowSize != 0)
//    {
//        memcpy(&overflowBuffer, &pBuffer[j-overflowSize], overflowSize);
//        
//    }
    
  //  std::cout << "Overflow size: " << overflowSize << std::endl;

   // std::cout << "End time: " << timestamp << std::endl;

    
    
   // std::cout << "TTL out:" << ttl_out << std::endl;
    
	//accumulator++;
    
    checkTTLState();

//    if (accumulator == 50)
//    {
//        //dev->SetWireInValue(0x01, 0x00); //, 0x06);
//        ttlOutputVal = 0;
//        //accumulator = 0;
//        //dev->UpdateWireIns();
//     //   std::cout << return_code << " " << i << std::endl; // number of samples found
//       // std::cout << "Start sample: " << firstSample << std::endl;
//    } else if (accumulator > 100) {
//        //dev->SetWireInValue(0x01, 0xFF);//, 0x06);
//        //ttlOutputVal = 1;
//        accumulator = 0;
//        //dev->UpdateWireIns();
//    }
    
    
    
	return true;

}

void FPGAThread::checkTTLState()
    {
    if (sn->getTTLState() != ttlState)
    {
        ttlState = sn->getTTLState();
        
        if (ttlState == 1)
        {
            dev->SetWireInValue(0x01, 0xFF);
        } else {
            dev->SetWireInValue(0x01, 0x00);
        }
        
        dev->UpdateWireIns();
    }
}
    
void FPGAThread::setOutputHigh()
{
    dev->SetWireInValue(0x01, 0x01); //, 0x06);
    
    dev->UpdateWireIns();

   
}

void FPGAThread::setOutputLow()
{
    dev->SetWireInValue(0x01, 0x00); //, 0x06);

    dev->UpdateWireIns();
}

bool FPGAThread::initializeFPGA(bool verbose)
{

	std::cout << "okCFrontPanel found " << dev->GetDeviceCount() << " devices." << std::endl;

	if (okCFrontPanel::NoError != dev->OpenBySerial()) {
		if (verbose)
			printf("Device could not be opened.  Is one connected?\n");
		return false;
	}
	
	if (verbose)
		printf("Found a device: %s\n", dev->GetBoardModelString(dev->GetBoardModel()).c_str());

	dev->LoadDefaultPLLConfiguration();	

	// Get some general information about the XEM.
	if (verbose) {
		std::string str;
		printf("Device firmware version: %d.%d\n", dev->GetDeviceMajorVersion(), dev->GetDeviceMinorVersion());
		str = dev->GetSerialNumber();
		printf("Device serial number: %s\n", str.c_str());
		str = dev->GetDeviceID();
		printf("Device device ID: %s\n", str.c_str());
	}
	// Download the configuration file.
	if (okCFrontPanel::NoError != dev->ConfigureFPGA(bitfile)) {
		if (verbose)
			printf("FPGA configuration failed.\n");
		return false;
	} else {
		printf("Bitfile uploaded.\n");
	}

	// Check for FrontPanel support in the FPGA configuration.
	if (verbose) {
		if (dev->IsFrontPanelEnabled())
			printf("FrontPanel support is enabled.\n");
		else
			printf("FrontPanel support is not enabled.\n");
	}

	dev->SetWireInValue(0x01, 0);
    dev->UpdateWireIns();

	return true;

	// this is not executed (after returning true)
	dev->SetWireInValue(0x00, 1<<2);  // set reset bit in cmd wire to 1 and back to 0
	dev->UpdateWireIns();
	dev->SetWireInValue(0x00, 0<<2);  
	dev->UpdateWireIns();

}