/*----------------------------------------------------------------------------*/
/* Copyright (c) FIRST 2008. All Rights Reserved.							  */
/* Open Source Software - may be modified and shared by FRC teams. The code   */
/* must be accompanied by the FIRST BSD license file in $(WIND_BASE)/WPILib.  */
/*----------------------------------------------------------------------------*/

#include "ADXL345_SPI.h"
#include "DigitalInput.h"
#include "DigitalOutput.h"
//#include "NetworkCommunication/UsageReporting.h"
#include "SPI.h"

const uint8_t ADXL345_SPI::kPowerCtlRegister;
const uint8_t ADXL345_SPI::kDataFormatRegister;
const uint8_t ADXL345_SPI::kDataRegister;
constexpr double ADXL345_SPI::kGsPerLSB;

/**
 * Constructor.
 *
 * @param clk The GPIO the clock signal is wired to.
 * @param mosi The GPIO the MOSI (Master Out Slave In) signal is wired to.
 * @param miso The GPIO the MISO (Master In Slave Out) signal is wired to.
 * @param cs The GPIO the CS (Chip Select) signal is wired to.
 * @param range The range (+ or -) that the accelerometer will measure.
 */
ADXL345_SPI::ADXL345_SPI(DigitalOutput &clk, DigitalOutput &mosi, DigitalInput &miso,
	DigitalOutput &cs, DataFormat_Range range)
	: m_clk (NULL)
	, m_mosi (NULL)
	, m_miso (NULL)
	, m_cs (NULL)
	, m_spi (NULL)
{
	Init(&clk, &mosi, &miso, &cs, range);
}

/**
 * Constructor.
 *
 * @param clk The GPIO the clock signal is wired to.
 * @param mosi The GPIO the MOSI (Master Out Slave In) signal is wired to.
 * @param miso The GPIO the MISO (Master In Slave Out) signal is wired to.
 * @param cs The GPIO the CS (Chip Select) signal is wired to.
 * @param range The range (+ or -) that the accelerometer will measure.
 */
ADXL345_SPI::ADXL345_SPI(DigitalOutput *clk, DigitalOutput *mosi, DigitalInput *miso,
	DigitalOutput *cs, DataFormat_Range range)
	: m_clk (NULL)
	, m_mosi (NULL)
	, m_miso (NULL)
	, m_cs (NULL)
	, m_spi (NULL)
{
	Init(clk, mosi, miso, cs, range);
}

/**
 * Constructor.
 *
 * @param clk The GPIO the clock signal is wired to.
 * @param mosi The GPIO the MOSI (Master Out Slave In) signal is wired to.
 * @param miso The GPIO the MISO (Master In Slave Out) signal is wired to.
 * @param cs The GPIO the CS (Chip Select) signal is wired to.
 * @param range The range (+ or -) that the accelerometer will measure.
 */
ADXL345_SPI::ADXL345_SPI(uint32_t clk, uint32_t mosi, uint32_t miso,
		uint32_t cs, ADXL345_SPI::DataFormat_Range range)
	: m_clk (NULL)
	, m_mosi (NULL)
	, m_miso (NULL)
	, m_cs (NULL)
	, m_spi (NULL)
{
	m_clk = new DigitalOutput(clk);
	m_mosi = new DigitalOutput(mosi);
	m_miso = new DigitalInput(miso);
	m_cs = new DigitalOutput(cs);
	Init(m_clk, m_mosi, m_miso, m_cs, range);
}

/**
 * Internal common init function.
 */
void ADXL345_SPI::Init(DigitalOutput *clk, DigitalOutput *mosi, DigitalInput *miso,
	DigitalOutput *cs, DataFormat_Range range)
{
	if (clk != NULL && mosi != NULL && miso != NULL && cs != NULL)
	{
		m_spi = new SPI(clk, mosi, miso);
		m_spi->SetMSBFirst();
		m_spi->SetSampleDataOnRising();
		m_spi->SetSlaveSelect(cs, kChipSelect, false);
		m_spi->SetClockActiveLow();
		// 8-bit address and 8-bit data
		m_spi->SetBitsPerWord(16);
		m_spi->ApplyConfig();
		m_spi->ClearReceivedData();

		// Turn on the measurements
		m_spi->Write((kPowerCtlRegister << 8) | kPowerCtl_Measure);
		m_spi->Read();
		// Specify the data format to read
		m_spi->Write((kDataFormatRegister << 8) | kDataFormat_FullRes | (uint8_t)(range & 0x03));
		m_spi->Read();

		// 8-bit address and 16-bit data
		m_spi->SetBitsPerWord(24);
		m_spi->ApplyConfig();

		HALReport(HALUsageReporting::kResourceType_ADXL345, HALUsageReporting::kADXL345_SPI);
	}
}

/**
 * Destructor.
 */
ADXL345_SPI::~ADXL345_SPI()
{
	delete m_spi;
	m_spi = NULL;
	delete m_cs;
	m_cs = NULL;
	delete m_miso;
	m_miso = NULL;
	delete m_mosi;
	m_mosi = NULL;
	delete m_clk;
	m_clk = NULL;
}

/**
 * Get the acceleration of one axis in Gs.
 *
 * @param axis The axis to read from.
 * @return Acceleration of the ADXL345 in Gs.
 */
double ADXL345_SPI::GetAcceleration(ADXL345_SPI::Axes axis)
{
	int16_t rawAccel = 0;
	if(m_spi)
	{
		m_spi->Write(((kAddress_Read | kAddress_MultiByte | kDataRegister) + (uint8_t)axis) << 16);
		rawAccel = (uint16_t)m_spi->Read();

		// Sensor is little endian... swap bytes
		rawAccel = ((rawAccel >> 8) & 0xFF) | (rawAccel << 8);
	}
	return rawAccel * kGsPerLSB;
}

/**
 * Get the acceleration of all axes in Gs.
 *
 * @return Acceleration measured on all axes of the ADXL345 in Gs.
 */
ADXL345_SPI::AllAxes ADXL345_SPI::GetAccelerations()
{
	AllAxes data = AllAxes();
	int16_t rawData[3];
	if (m_spi)
	{
		tFrameMode mode;
		bool activeLow;

		// Backup original settings.
		DigitalOutput *cs = m_spi->GetSlaveSelect(&mode, &activeLow);
		uint32_t bitsPerWord = m_spi->GetBitsPerWord();

		// Initialize the chip select to inactive.
		cs->Set(activeLow);

		// Control the chip select manually.
		m_spi->SetSlaveSelect(NULL);
		// 8-bit address
		m_spi->SetBitsPerWord(8);
		m_spi->ApplyConfig();

		// Assert chip select.
		cs->Set(!activeLow);

		// Select the data address.
		m_spi->Write(kAddress_Read | kAddress_MultiByte | kDataRegister);
		m_spi->Read();

		// 16-bits for each axis
		m_spi->SetBitsPerWord(16);
		m_spi->ApplyConfig();

		for (int32_t i=0; i<3; i++)
		{
			// SPI Interface can't read enough data in a single transaction to read all axes at once.
			rawData[i] = (uint16_t)m_spi->Read(true);
			// Sensor is little endian... swap bytes
			rawData[i] = ((rawData[i] >> 8) & 0xFF) | (rawData[i] << 8);
		}

		// Deassert chip select.
		cs->Set(activeLow);

		// Restore original settings.
		m_spi->SetSlaveSelect(cs, mode, activeLow);
		m_spi->SetBitsPerWord(bitsPerWord);
		m_spi->ApplyConfig();

		data.XAxis = rawData[0] * kGsPerLSB;
		data.YAxis = rawData[1] * kGsPerLSB;
		data.ZAxis = rawData[2] * kGsPerLSB;
	}
	return data;
}
