/*
    PIP - Platform Independent Primitives
    File
    Ivan Pelipenko peri4ko@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 "pisharedmemory.h"

#include "piincludes_p.h"
#include "piliterals.h"
#include "pipropertystorage.h"
#if defined(LINUX) || defined(MAC_OS)
#  define SHM_POSIX
#endif
#ifdef WINDOWS

#endif
#if defined(QNX) || defined(ANDROID)

#endif
#ifdef MAC_OS
// #  include <fcntl.h>

#endif
#ifdef SHM_POSIX
#  include <fcntl.h>
#  include <sys/mman.h>
#  include <sys/stat.h>
#endif


//! \class PISharedMemory pisharedmemory.h
//! \details
//! \~english
//!
//!
//! \~russian
//! Разделяемая память используется как единое хранилище данных,
//! доступное различным процессам по имени. При первом открытии
//! объекта разделяемой памяти выделяется \a size() байт, по умолчанию
//! 65 Кб. Все процессы должны использовать один и тот же \a size()
//! во избежании ошибок.
//!
//! У объекта разделяемой памяти нету позиции чтения/записи,
//! каждый вызов \a read() или \a write() обращается
//! к началу памяти. Для работы с конкретным участком памяти
//! используются перегруженные методы с указанием "offset".
//!


REGISTER_DEVICE(PISharedMemory)

PRIVATE_DEFINITION_START(PISharedMemory)
	PIByteArray name;
#ifdef WINDOWS
	HANDLE map;
	void * data;
#endif
#ifdef SHM_POSIX
	void * data;
	bool owner;
#endif
PRIVATE_DEFINITION_END(PISharedMemory)


PISharedMemory::PISharedMemory(): PIIODevice() {
	initPrivate();
	dsize = 64_KiB;
}


PISharedMemory::PISharedMemory(const PIString & shm_name, int size, PIIODevice::DeviceMode mode): PIIODevice(shm_name, mode) {
	initPrivate();
	dsize = size;
	if (!shm_name.isEmpty()) open();
}


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


bool PISharedMemory::openDevice() {
	close();
	// piCoutObj << "try open" << path() << dsize;
#ifdef WINDOWS
	PRIVATE->name = ("PIP_SHM_" + path()).toByteArray();
	PRIVATE->name.push_back(0);
	const char * nm = (const char *)PRIVATE->name.data();
	PRIVATE->map    = OpenFileMappingA(FILE_MAP_ALL_ACCESS, FALSE, nm);
	// piCoutObj << "open map =" << ullong(PRIVATE->map);
	if (!PRIVATE->map) {
		PRIVATE->map = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, (DWORD)dsize, nm);
		// piCoutObj << "create map =" << ullong(PRIVATE->map);
		if (!PRIVATE->map) {
			piCoutObj << "CreateFileMapping error," << errorString();
			return false;
		}
	}
	PRIVATE->data = MapViewOfFile(PRIVATE->map, FILE_MAP_ALL_ACCESS, 0, 0, dsize);
	if (!PRIVATE->data) {
		CloseHandle(PRIVATE->map);
		piCoutObj << "MapViewOfFile error," << errorString();
		return false;
	}
	// piCout << PRIVATE->map << PRIVATE->data;
#endif
#ifdef SHM_POSIX
	PRIVATE->name = ("/pip_shm_" + path()).toByteArray();
	PRIVATE->name.push_back(0);
	int fd = shm_open((const char *)PRIVATE->name.data(), O_RDWR, 0777);
	// piCoutObj << "try open" << PICoutManipulators::Hex << fd;
	if (fd < 0) {
		// piCoutObj << "shm_open error," << errorString();
		fd = shm_open((const char *)PRIVATE->name.data(), O_RDWR | O_CREAT, 0777);
		// piCoutObj << "try create" << PICoutManipulators::Hex << (m | O_CREAT) << fd;
		if (fd < 0) {
			piCoutObj << "shm_open error," << errorString();
			return false;
		}
		PRIVATE->owner = true;
		// piCoutObj << "created" << fd;
	}
	if (fd >= 0) {
		auto _r = ftruncate(fd, dsize);
		NO_UNUSED(_r);
	}
	PRIVATE->data = mmap(0, dsize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
	::close(fd);
	if (PRIVATE->data == MAP_FAILED) {
		piCoutObj << "mmap error," << errorString();
		if (PRIVATE->owner) shm_unlink((const char *)PRIVATE->name.data());
		PRIVATE->data  = nullptr;
		PRIVATE->owner = false;
		return false;
	}
	// piCoutObj << "opened" << PRIVATE->data;
#endif
	return true;
}


bool PISharedMemory::closeDevice() {
#ifdef WINDOWS
	if (PRIVATE->data) UnmapViewOfFile(PRIVATE->data);
	if (PRIVATE->map) CloseHandle(PRIVATE->map);
#endif
#ifdef SHM_POSIX
	// piCoutObj << "close" << PIString(PRIVATE->name) << PRIVATE->data;
	if (PRIVATE->data) munmap(PRIVATE->data, dsize);
	if (PRIVATE->owner) {
		// piCout << "unlink" << PIString(PRIVATE->name);
		shm_unlink((const char *)PRIVATE->name.data());
	}
	// if (fd > 0)
#endif
	initPrivate();
	return true;
}


PIString PISharedMemory::constructFullPathDevice() const {
	PIString ret;
	ret += path() + ":" + PIString::fromNumber(dsize);
	return ret;
}


void PISharedMemory::configureFromFullPathDevice(const PIString & full_path) {
	initPrivate();
	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: dsize = p.toInt(); break;
		}
	}
}


PIPropertyStorage PISharedMemory::constructVariantDevice() const {
	PIPropertyStorage ret;
	ret.addProperty("path", path());
	ret.addProperty("size", dsize);
	return ret;
}


void PISharedMemory::configureFromVariantDevice(const PIPropertyStorage & d) {
	setPath(d.propertyValueByName("path").toString());
	setSize(d.propertyValueByName("size").toInt());
}


void PISharedMemory::initPrivate() {
#ifdef WINDOWS
	PRIVATE->map  = 0;
	PRIVATE->data = 0;
#endif
#ifdef SHM_POSIX
	PRIVATE->data  = 0;
	PRIVATE->owner = false;
#endif
}


PIByteArray PISharedMemory::readAll() {
	if (dsize <= 0) return PIByteArray();
#ifdef WINDOWS
	if (!PRIVATE->data) return PIByteArray();
#endif
#ifdef SHM_POSIX
	if (!PRIVATE->data) return PIByteArray();
#endif
	PIByteArray a(dsize);
	read(a.data(), a.size_s());
	return a;
}


llong PISharedMemory::size() const {
	if (isClosed()) return -1;
	return dsize;
}


void PISharedMemory::setSize(llong s) {
	bool o = isOpened();
	if (o) close();
	dsize = s;
	if (o) open();
}


int PISharedMemory::read(void * read_to, int max_size) {
	return read(read_to, max_size, 0);
}


int PISharedMemory::read(void * read_to, int max_size, int offset) {
	if (offset < 0 || max_size < 0 || offset > dsize - max_size) return -1;
#ifdef WINDOWS
	if (!PRIVATE->data) return -1;
	CopyMemory(read_to, &(((char *)(PRIVATE->data))[offset]), max_size);
	return max_size;
#endif
#ifdef SHM_POSIX
	if (!PRIVATE->data) return -1;
	memcpy(read_to, &(((char *)(PRIVATE->data))[offset]), max_size);
	return max_size;
#endif
	return -1;
}


int PISharedMemory::write(const void * data, int max_size) {
	return write(data, max_size, 0);
}


int PISharedMemory::write(const void * data, int max_size, int offset) {
	if (offset < 0 || max_size < 0 || offset > dsize - max_size) return -1;
#ifdef WINDOWS
	if (!PRIVATE->data) return -1;
	CopyMemory(&(((char *)(PRIVATE->data))[offset]), data, max_size);
	return max_size;
#endif
#ifdef SHM_POSIX
	if (!PRIVATE->data) return -1;
	memcpy(&(((char *)(PRIVATE->data))[offset]), data, max_size);
	return max_size;
#endif
	return -1;
}
