/*
    PIP - Platform Independent Primitives
    CAN
    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 "pican.h"

#include "pipropertystorage.h"
#include "piwaitevent_p.h"
#if !defined(WINDOWS) && !defined(MAC_OS) && !defined(MICRO_PIP)
#  define PIP_CAN
#endif
#ifdef PIP_CAN
#  include <fcntl.h>
#  include <linux/can.h>
#  include <linux/can/raw.h>
#  include <net/if.h>
#  include <sys/ioctl.h>
#  ifndef AF_CAN
#	define AF_CAN 29
#  endif
#  ifndef PF_CAN
#	define PF_CAN AF_CAN
#  endif
#endif


REGISTER_DEVICE(PICAN)


PRIVATE_DEFINITION_START(PICAN)
	PIWaitEvent event;
PRIVATE_DEFINITION_END(PICAN)


PICAN::PICAN(const PIString & path, PIIODevice::DeviceMode mode): PIIODevice(path, mode) {
	setThreadedReadBufferSize(256);
	setPath(path);
	can_id = 0;
	sock   = -1;
	PRIVATE->event.create();
}


PICAN::~PICAN() {
	stopAndWait();
	close();
	PRIVATE->event.destroy();
}


bool PICAN::openDevice() {
#ifdef PIP_CAN
	piCout << "PICAN open device" << path();
	sock = socket(PF_CAN, SOCK_RAW, CAN_RAW);
	if (sock < 0) {
		piCoutObj << "Error! while opening socket";
		sock = -1;
		return false;
	}
	fcntl(sock, F_SETFL, fcntl(sock, F_GETFL) | O_NONBLOCK);
	ifreq ifr;
	piZeroMemory(ifr);
	strncpy(ifr.ifr_name, path().dataAscii(), sizeof(ifr.ifr_name));
	piCout << "PICAN try to get interface index...";
	if (ioctl(sock, SIOCGIFINDEX, &ifr) < 0) {
		piCoutObj << "Error! while determin the interface ioctl";
		::close(sock);
		sock = -1;
		return false;
	}
	struct timeval tv;
	tv.tv_sec  = 1;
	tv.tv_usec = 0;
	if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof tv) < 0) {
		piCoutObj << "Error! while setting socket receive timeout";
	}
	// bind socket to all CAN interface
	sockaddr_can addr;
	addr.can_family  = AF_CAN;
	addr.can_ifindex = ifr.ifr_ifindex;
	piCout << "PICAN try to bind socket to interface" << ifr.ifr_ifindex;
	if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
		piCoutObj << "Error! while binding socket";
		::close(sock);
		sock = -1;
		return false;
	}
	piCout << "PICAN Open OK!";
	return true;
#else
	piCoutObj << "PICAN not implemented on windows";
	return false;
#endif
}


bool PICAN::closeDevice() {
#ifdef PIP_CAN
	interrupt();
	if (sock != -1) {
		::shutdown(sock, SHUT_RDWR);
		::close(sock);
		sock = -1;
	}
#endif
	return true;
}


ssize_t PICAN::readDevice(void * read_to, ssize_t max_size) {
#ifdef PIP_CAN
	if (sock == -1) return -1;
	// piCout << "PICAN read";
	can_frame frame = {};
	ssize_t ret     = 0;
	if (PRIVATE->event.wait(sock)) ret = ::read(sock, &frame, sizeof(can_frame));
	if (ret < 0) { /*piCoutObj << "Error while read CAN frame " << ret;*/
		return -1;
	}
	// piCoutObj << "receive CAN frame Id =" << frame.can_id;
	memcpy(read_to, frame.data, piMini(frame.can_dlc, max_size));
	readed_id = frame.can_id;
	return piMini(frame.can_dlc, max_size);
#endif
	return 0;
}


ssize_t PICAN::writeDevice(const void * data, ssize_t max_size) {
#ifdef PIP_CAN
	if (sock == -1) return -1;
	// piCout << "PICAN write" << can_id << max_size;
	if (max_size > 8) {
		piCoutObj << "Can't send CAN frame bigger than 8 bytes (requested " << max_size << ")!";
		return -1;
	}
	can_frame frame;
	frame.can_id  = can_id;
	frame.can_dlc = max_size;
	memcpy(frame.data, data, max_size);
	ssize_t ret = 0;
	ret         = ::write(sock, &frame, sizeof(can_frame));
	if (ret < 0) {
		piCoutObj << "Error while send CAN frame " << ret;
		return -1;
	}
	return max_size;
#endif
	return 0;
}


void PICAN::setCANID(int id) {
	can_id = id;
}


int PICAN::CANID() const {
	return can_id;
}


int PICAN::readedCANID() const {
	return readed_id;
}


void PICAN::interrupt() {
	PRIVATE->event.interrupt();
}


PIString PICAN::constructFullPathDevice() const {
	PIString ret;
	ret += path() + ":" + PIString::fromNumber(CANID(), 16);
	return ret;
}


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


PIPropertyStorage PICAN::constructVariantDevice() const {
	PIPropertyStorage ret;
	ret.addProperty("path", path());
	ret.addProperty("CAN ID", PIString::fromNumber(CANID(), 16));
	return ret;
}


void PICAN::configureFromVariantDevice(const PIPropertyStorage & d) {
	setPath(d.propertyValueByName("path").toString());
	setCANID(d.propertyValueByName("CAN ID").toString().toInt(16));
}
