/*
	This file is part of Fennix Kernel.

	Fennix Kernel 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.

	Fennix Kernel 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 Fennix Kernel. If not, see <https://www.gnu.org/licenses/>.
*/

#include <filesystem.hpp>

#ifdef DEBUG
const char *SeekStrings[] =
	{"SEEK_SET",
	 "SEEK_CUR",
	 "SEEK_END"};
#endif

namespace vfs
{
	size_t RefNode::read(uint8_t *Buffer, size_t Size)
	{
		if (this->SymlinkTo)
			return this->SymlinkTo->read(Buffer, Size);

		return this->node->read(Buffer, Size, this->FileOffset.load());
	}

	size_t RefNode::write(uint8_t *Buffer, size_t Size)
	{
		if (this->SymlinkTo)
			return this->SymlinkTo->write(Buffer, Size);

		return this->node->write(Buffer, Size, this->FileOffset.load());
	}

	off_t RefNode::seek(off_t Offset, int Whence)
	{
		if (this->SymlinkTo)
			return this->SymlinkTo->seek(Offset, Whence);

		// debug("Current offset is %d", this->Offset.load());
		switch (Whence)
		{
		case SEEK_SET:
		{
			if (Offset > this->FileSize)
				return -EINVAL;

			if (Offset < 0)
			{
				fixme("Negative offset %d is not implemented", Offset);
				Offset = 0;
			}

			if (Offset > this->FileSize)
			{
				fixme("Offset %d is bigger than file size %d",
					  Offset, this->FileSize);
				Offset = this->FileSize;
			}

			this->FileOffset.store(Offset);
			break;
		}
		case SEEK_CUR:
		{
			off_t NewOffset = off_t(this->FileOffset.load()) + Offset;
			if (NewOffset > this->FileSize ||
				NewOffset < 0)
			{
				return -EINVAL;
			}

			this->FileOffset.store(NewOffset);
			break;
		}
		case SEEK_END:
		{
			off_t NewOffset = this->FileSize + Offset;
			if (NewOffset > this->FileSize ||
				NewOffset < 0)
			{
				return -EINVAL;
			}

			this->FileOffset.store(NewOffset);
			break;
		}
		default:
		{
			error("Invalid whence!");
			return -EINVAL;
		}
		}

		off_t RetOffset = off_t(this->FileOffset.load());
		// debug("( %d %ld %s[%d] ) -> %d",
		// 	  Offset, this->Offset.load(),
		// 	  SeekStrings[Whence], Whence,
		// 	  RetOffset);
		return RetOffset;
	}

	int RefNode::ioctl(unsigned long Request, void *Argp)
	{
		if (this->SymlinkTo)
			return this->SymlinkTo->ioctl(Request, Argp);

		return this->node->ioctl(Request, Argp);
	}

	RefNode::RefNode(Node *node)
	{
		this->node = node;
		this->FileSize = node->Size;
		if (this->node->Type == SYMLINK)
		{
			if (!this->node->SymlinkTarget)
			{
				this->node->SymlinkTarget =
					node->vFS->GetNodeFromPath(this->node->Symlink);
			}

			if (!this->node->SymlinkTarget)
			{
				error("Symlink target %s not found!",
					  this->node->Symlink);
				return;
			}

			/* not standard but useful in kernel-space */
			this->node->Size = this->node->SymlinkTarget->Size;
			this->SymlinkTo = this->node->SymlinkTarget->CreateReference();
		}

		debug("Created reference node for %s [%#lx]",
			  this->node->FullPath, (uintptr_t)this);
	}

	RefNode::~RefNode()
	{
		if (this->SymlinkTo)
			this->node->SymlinkTarget->RemoveReference(this);
		this->node->RemoveReference(this);

		debug("Destroyed reference node for %s [%#lx]",
			  this->node->FullPath, (uintptr_t)this);
	}
}