/*
    PIP - Platform Independent Primitives
    SPI
    Andrey Bychkov work.a.b@yandex.ru

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser 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 Lesser General Public License for more details.

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

#include "pispi.h"

#include "piincludes_p.h"
#include "pipropertystorage.h"
#ifdef LINUX
#  define PIP_SPI
#endif
#ifdef PIP_SPI
#  include <fcntl.h>
#  include <linux/spi/spidev.h>
#  include <sys/ioctl.h>
#endif


PRIVATE_DEFINITION_START(PISPI)
#ifdef PIP_SPI
	int fd;
	spi_ioc_transfer spi_ioc_tr;
#endif
PRIVATE_DEFINITION_END(PISPI)


REGISTER_DEVICE(PISPI)


PISPI::PISPI(const PIString & path, uint speed, PIIODevice::DeviceMode mode): PIIODevice(path, mode) {
#ifdef MICRO_PIP
	setThreadedReadBufferSize(512);
#else
	setThreadedReadBufferSize(1024);
#endif
	setPath(path);
	setSpeed(speed);
	setBits(8);
	spi_mode = 0;
	if (mode == ReadOnly) piCoutObj << "error, SPI can't work in ReadOnly mode";
#ifdef PIP_SPI
	PRIVATE->fd = 0;
#endif
}


PISPI::~PISPI() {
	stop();
	close();
}


void PISPI::setSpeed(uint speed_hz) {
	spi_speed = speed_hz;
}


void PISPI::setBits(uchar bits) {
	spi_bits = bits;
}


void PISPI::setParameter(PISPI::Parameters parameter, bool on) {
	PIFlags<Parameters> cp = (PIFlags<Parameters>)spi_mode;
	cp.setFlag(parameter, on);
	spi_mode = (int)cp;
}


bool PISPI::isParameterSet(PISPI::Parameters parameter) const {
	PIFlags<Parameters> cp = (PIFlags<Parameters>)spi_mode;
	return cp[parameter];
}


ssize_t PISPI::bytesAvailable() const {
	return recv_buf.size();
}


bool PISPI::openDevice() {
#ifdef PIP_SPI
	int ret     = 0;
	// piCoutObj << "open device" << path();
	PRIVATE->fd = ::open(path().dataAscii(), O_RDWR);
	if (PRIVATE->fd < 0) {
		piCoutObj << "can't open device";
		return false;
	}
	// piCoutObj << "set mode" << spi_mode;
	ret = ioctl(PRIVATE->fd, SPI_IOC_WR_MODE, &spi_mode);
	if (ret == -1) {
		piCoutObj << "can't set spi write mode";
		::close(PRIVATE->fd);
		PRIVATE->fd = -1;
		return false;
	}
	// piCoutObj << "set bits" << spi_bits;
	ret = ioctl(PRIVATE->fd, SPI_IOC_WR_BITS_PER_WORD, &spi_bits);
	if (ret == -1) {
		piCoutObj << "can't set bits per word";
		::close(PRIVATE->fd);
		PRIVATE->fd = -1;
		return false;
	}
	// piCoutObj << "set speed" << spi_speed;
	ret = ioctl(PRIVATE->fd, SPI_IOC_WR_MAX_SPEED_HZ, &spi_speed);
	if (ret == -1) {
		piCoutObj << "can't set max write speed hz";
		::close(PRIVATE->fd);
		PRIVATE->fd = -1;
		return false;
	}
	piCoutObj << "SPI open" << path() << "speed:" << spi_speed / 1000 << "KHz"
			  << "mode" << spi_mode << "bits" << spi_bits;
	PRIVATE->spi_ioc_tr.delay_usecs   = 0;
	PRIVATE->spi_ioc_tr.speed_hz      = 0;
	PRIVATE->spi_ioc_tr.bits_per_word = spi_bits;
	return true;
#else
	piCoutObj << "PISPI not implemented on windows";
	return false;
#endif
}


bool PISPI::closeDevice() {
#ifdef PIP_SPI
	if (PRIVATE->fd) ::close(PRIVATE->fd);
#endif
	return true;
}


ssize_t PISPI::readDevice(void * read_to, ssize_t max_size) {
	ssize_t sz = piMini(recv_buf.size_s(), max_size);
	memcpy(read_to, recv_buf.data(), sz);
	recv_buf.remove(0, sz);
	return sz;
}


ssize_t PISPI::writeDevice(const void * data, ssize_t max_size) {
#ifdef PIP_SPI
	if (max_size > 0) {
		if (tx_buf.size_s() != max_size) {
			tx_buf.resize(max_size);
			rx_buf.resize(max_size);
			PRIVATE->spi_ioc_tr.tx_buf = (ulong)(tx_buf.data());
			PRIVATE->spi_ioc_tr.rx_buf = (ulong)(rx_buf.data());
			PRIVATE->spi_ioc_tr.len    = max_size;
		}
		memcpy(tx_buf.data(), data, max_size);
		int ret;
		// piCoutObj << "write" << max_size << tx_buf.size();
		ret = ioctl(PRIVATE->fd, SPI_IOC_MESSAGE(1), &PRIVATE->spi_ioc_tr);
		if (ret < 1) {
			piCoutObj << "can't send spi message" << ret;
			return -1;
		}
		if (canRead()) recv_buf.append(rx_buf);
		if (recv_buf.size_s() > threadedReadBufferSize()) recv_buf.resize(threadedReadBufferSize());
		return max_size;
	}
#endif
	return 0;
}


PIString PISPI::constructFullPathDevice() const {
	PIString ret;
	ret += path() + ":" + PIString::fromNumber((int)speed()) + ":" + PIString::fromNumber((int)bits()) + ":" +
	       PIString::fromNumber((int)parameters());
	return ret;
}


void PISPI::configureFromFullPathDevice(const PIString & full_path) {
	PIStringList pl = full_path.split(":");
	for (int i = 0; i < pl.size_s(); ++i) {
		PIString p(pl[i].trimmed());
		switch (i) {
		case 0: setPath(p); break;
		case 1: setSpeed(p.toInt()); break;
		case 2: setBits(p.toInt()); break;
		case 3: setParameters(p.toInt()); break;
		default: break;
		}
	}
}


PIPropertyStorage PISPI::constructVariantDevice() const {
	PIPropertyStorage ret;
	ret.addProperty("path", path());
	ret.addProperty("speed", int(speed()));
	ret.addProperty("bits", int(bits()));
	ret.addProperty("clock inverse", isParameterSet(ClockInverse));
	ret.addProperty("clock phase shift", isParameterSet(ClockPhaseShift));
	return ret;
}


void PISPI::configureFromVariantDevice(const PIPropertyStorage & d) {
	setPath(d.propertyValueByName("path").toString());
	setSpeed(d.propertyValueByName("speed").toInt());
	setBits(d.propertyValueByName("bits").toInt());
	setParameter(ClockInverse, d.propertyValueByName("clock inverse").toBool());
	setParameter(ClockPhaseShift, d.propertyValueByName("clock phase shift").toBool());
}
