#include <kernel/FS/Pipe.h>
#include <kernel/FS/VirtualFileSystem.h>
#include <kernel/Networking/NetworkManager.h>
#include <kernel/OpenFileDescriptorSet.h>

#include <fcntl.h>
#include <sys/socket.h>

namespace Kernel
{

	OpenFileDescriptorSet::OpenFileDescriptorSet(const Credentials& credentials)
		: m_credentials(credentials)
	{

	}

	OpenFileDescriptorSet::~OpenFileDescriptorSet()
	{
		close_all();
	}

	OpenFileDescriptorSet& OpenFileDescriptorSet::operator=(OpenFileDescriptorSet&& other)
	{
		for (size_t i = 0; i < m_open_files.size(); i++)
			m_open_files[i] = BAN::move(other.m_open_files[i]);
		return *this;
	}

	BAN::ErrorOr<void> OpenFileDescriptorSet::clone_from(const OpenFileDescriptorSet& other)
	{
		close_all();

		for (int fd = 0; fd < (int)other.m_open_files.size(); fd++)
		{
			if (other.validate_fd(fd).is_error())
				continue;

			auto& open_file = other.m_open_files[fd];

			VirtualFileSystem::File temp_file;
			temp_file.inode = open_file->inode();
			TRY(temp_file.canonical_path.append(open_file->path()));

			auto result = BAN::RefPtr<OpenFileDescription>::create(BAN::move(temp_file), open_file->offset, open_file->flags);

			if (result.is_error())
			{
				close_all();
				return result.error();
			}

			m_open_files[fd] = result.release_value();

			if (m_open_files[fd]->path() == "<pipe wr>"_sv)
			{
				ASSERT(m_open_files[fd]->inode()->is_pipe());
				static_cast<Pipe*>(m_open_files[fd]->inode().ptr())->clone_writing();
			}
		}

		return {};
	}

	BAN::ErrorOr<int> OpenFileDescriptorSet::open(VirtualFileSystem::File&& file, int flags)
	{
		ASSERT(file.inode);

		if (flags & ~(O_ACCMODE | O_NOFOLLOW | O_APPEND | O_TRUNC | O_CLOEXEC | O_TTY_INIT | O_NOCTTY | O_DIRECTORY | O_CREAT | O_EXCL | O_NONBLOCK))
			return BAN::Error::from_errno(ENOTSUP);

		if ((flags & O_ACCMODE) != O_RDWR && __builtin_popcount(flags & O_ACCMODE) != 1)
			return BAN::Error::from_errno(EINVAL);

		if ((flags & O_DIRECTORY) && !file.inode->mode().ifdir())
			return BAN::Error::from_errno(ENOTDIR);

		if ((flags & O_TRUNC) && (flags & O_WRONLY) && file.inode->mode().ifreg())
			TRY(file.inode->truncate(0));

		int fd = TRY(get_free_fd());
		m_open_files[fd] = TRY(BAN::RefPtr<OpenFileDescription>::create(BAN::move(file), 0, flags));

		return fd;
	}

	BAN::ErrorOr<int> OpenFileDescriptorSet::open(BAN::StringView absolute_path, int flags)
	{
		return open(TRY(VirtualFileSystem::get().file_from_absolute_path(m_credentials, absolute_path, flags)), flags);
	}

	BAN::ErrorOr<int> OpenFileDescriptorSet::socket(int domain, int type, int protocol)
	{
		bool valid_protocol = true;

		Socket::Domain sock_domain;
		switch (domain)
		{
			case AF_INET:
				sock_domain = Socket::Domain::INET;
				break;
			case AF_INET6:
				sock_domain = Socket::Domain::INET6;
				break;
			case AF_UNIX:
				sock_domain = Socket::Domain::UNIX;
				valid_protocol = false;
				break;
			default:
				return BAN::Error::from_errno(EPROTOTYPE);
		}

		int extra_flags = 0;
		if (type & SOCK_NONBLOCK)
			extra_flags |= O_NONBLOCK;
		if (type & SOCK_CLOEXEC)
			extra_flags |= O_CLOEXEC;
		type &= ~(SOCK_NONBLOCK | SOCK_CLOEXEC);

		Socket::Type sock_type;
		switch (type)
		{
			case SOCK_STREAM:
				sock_type = Socket::Type::STREAM;
				if (protocol != IPPROTO_TCP)
					valid_protocol = false;
				break;
			case SOCK_DGRAM:
				sock_type = Socket::Type::DGRAM;
				if (protocol != IPPROTO_UDP)
					valid_protocol = false;
				break;
			case SOCK_SEQPACKET:
				sock_type = Socket::Type::SEQPACKET;
				valid_protocol = false;
				break;
			default:
				return BAN::Error::from_errno(EPROTOTYPE);
		}

		if (protocol && !valid_protocol)
			return BAN::Error::from_errno(EPROTONOSUPPORT);

		auto socket = TRY(NetworkManager::get().create_socket(sock_domain, sock_type, 0777, m_credentials.euid(), m_credentials.egid()));

		int fd = TRY(get_free_fd());
		m_open_files[fd] = TRY(BAN::RefPtr<OpenFileDescription>::create(VirtualFileSystem::File(socket, "<socket>"_sv), 0, O_RDWR | extra_flags));
		return fd;
	}

	BAN::ErrorOr<void> OpenFileDescriptorSet::pipe(int fds[2])
	{
		TRY(get_free_fd_pair(fds));

		auto pipe = TRY(Pipe::create(m_credentials));
		m_open_files[fds[0]] = TRY(BAN::RefPtr<OpenFileDescription>::create(VirtualFileSystem::File(pipe, "<pipe rd>"_sv), 0, O_RDONLY));
		m_open_files[fds[1]] = TRY(BAN::RefPtr<OpenFileDescription>::create(VirtualFileSystem::File(pipe, "<pipe wr>"_sv), 0, O_WRONLY));

		return {};
	}

	BAN::ErrorOr<int> OpenFileDescriptorSet::dup(int fildes)
	{
		TRY(validate_fd(fildes));

		int result = TRY(get_free_fd());
		m_open_files[result] = m_open_files[fildes];

		if (m_open_files[result]->path() == "<pipe wr>"_sv)
		{
			ASSERT(m_open_files[result]->inode()->is_pipe());
			static_cast<Pipe*>(m_open_files[result]->inode().ptr())->clone_writing();
		}

		return result;
	}

	BAN::ErrorOr<int> OpenFileDescriptorSet::dup2(int fildes, int fildes2)
	{
		if (fildes2 < 0 || fildes2 >= (int)m_open_files.size())
			return BAN::Error::from_errno(EBADF);

		TRY(validate_fd(fildes));
		if (fildes == fildes2)
			return fildes;

		(void)close(fildes2);

		m_open_files[fildes2] = m_open_files[fildes];
		m_open_files[fildes2]->flags &= ~O_CLOEXEC;

		if (m_open_files[fildes2]->path() == "<pipe wr>"_sv)
		{
			ASSERT(m_open_files[fildes2]->inode()->is_pipe());
			static_cast<Pipe*>(m_open_files[fildes2]->inode().ptr())->clone_writing();
		}

		return fildes;
	}

	BAN::ErrorOr<int> OpenFileDescriptorSet::fcntl(int fd, int cmd, int extra)
	{
		TRY(validate_fd(fd));

		switch (cmd)
		{
			case F_GETFD:
				return m_open_files[fd]->flags & FD_CLOEXEC;
			case F_SETFD:
				m_open_files[fd]->flags &= ~FD_CLOEXEC;
				m_open_files[fd]->flags |= extra & FD_CLOEXEC;
				return 0;
			case F_GETFL:
				return m_open_files[fd]->flags & ~(O_APPEND | O_DSYNC | O_NONBLOCK | O_RSYNC | O_SYNC | O_ACCMODE);
			case F_SETFL:
				m_open_files[fd]->flags &= ~O_NONBLOCK;
				m_open_files[fd]->flags |= extra & O_NONBLOCK;
				return 0;
			default:
				break;
		}

		return BAN::Error::from_errno(ENOTSUP);
	}

	BAN::ErrorOr<off_t> OpenFileDescriptorSet::seek(int fd, off_t offset, int whence)
	{
		TRY(validate_fd(fd));

		off_t base_offset;
		switch (whence)
		{
			case SEEK_SET:
				base_offset = 0;
				break;
			case SEEK_CUR:
				base_offset = m_open_files[fd]->offset;
				break;
			case SEEK_END:
				base_offset = m_open_files[fd]->inode()->size();
				break;
			default:
				return BAN::Error::from_errno(EINVAL);
		}

		const off_t new_offset = base_offset + offset;
		if (new_offset < 0)
			return BAN::Error::from_errno(EINVAL);

		m_open_files[fd]->offset = new_offset;

		return new_offset;
	}

	BAN::ErrorOr<off_t> OpenFileDescriptorSet::tell(int fd) const
	{
		TRY(validate_fd(fd));
		return m_open_files[fd]->offset;
	}

	BAN::ErrorOr<void> OpenFileDescriptorSet::truncate(int fd, off_t length)
	{
		TRY(validate_fd(fd));
		return m_open_files[fd]->inode()->truncate(length);
	}

	BAN::ErrorOr<void> OpenFileDescriptorSet::close(int fd)
	{
		TRY(validate_fd(fd));

		if (m_open_files[fd]->path() == "<pipe wr>"_sv)
		{
			ASSERT(m_open_files[fd]->inode()->is_pipe());
			static_cast<Pipe*>(m_open_files[fd]->inode().ptr())->close_writing();
		}

		m_open_files[fd].clear();

		return {};
	}

	void OpenFileDescriptorSet::close_all()
	{
		for (int fd = 0; fd < (int)m_open_files.size(); fd++)
			(void)close(fd);
	}

	void OpenFileDescriptorSet::close_cloexec()
	{
		for (int fd = 0; fd < (int)m_open_files.size(); fd++)
		{
			if (validate_fd(fd).is_error())
				continue;
			if (m_open_files[fd]->flags & O_CLOEXEC)
				(void)close(fd);
		}
	}

	BAN::ErrorOr<size_t> OpenFileDescriptorSet::read(int fd, BAN::ByteSpan buffer)
	{
		TRY(validate_fd(fd));
		auto& open_file = m_open_files[fd];
		if (!(open_file->flags & O_RDONLY))
			return BAN::Error::from_errno(EBADF);
		if ((open_file->flags & O_NONBLOCK) && !open_file->inode()->can_read())
			return 0;
		size_t nread = TRY(open_file->inode()->read(open_file->offset, buffer));
		open_file->offset += nread;
		return nread;
	}

	BAN::ErrorOr<size_t> OpenFileDescriptorSet::write(int fd, BAN::ConstByteSpan buffer)
	{
		TRY(validate_fd(fd));
		auto& open_file = m_open_files[fd];
		if (!(open_file->flags & O_WRONLY))
			return BAN::Error::from_errno(EBADF);
		if ((open_file->flags & O_NONBLOCK) && !open_file->inode()->can_write())
			return 0;
		if (open_file->flags & O_APPEND)
			open_file->offset = open_file->inode()->size();
		size_t nwrite = TRY(open_file->inode()->write(open_file->offset, buffer));
		open_file->offset += nwrite;
		return nwrite;
	}

	BAN::ErrorOr<size_t> OpenFileDescriptorSet::read_dir_entries(int fd, struct dirent* list, size_t list_len)
	{
		TRY(validate_fd(fd));
		auto& open_file = m_open_files[fd];
		if (!(open_file->flags & O_RDONLY))
			return BAN::Error::from_errno(EACCES);
		for (;;)
		{
			auto ret = open_file->inode()->list_next_inodes(open_file->offset++, list, list_len);
			if (ret.is_error() && ret.error().get_error_code() == ENODATA)
				continue;
			return ret;
		}
	}

	BAN::ErrorOr<VirtualFileSystem::File> OpenFileDescriptorSet::file_of(int fd) const
	{
		TRY(validate_fd(fd));
		return TRY(m_open_files[fd]->file.clone());
	}

	BAN::ErrorOr<BAN::StringView> OpenFileDescriptorSet::path_of(int fd) const
	{
		TRY(validate_fd(fd));
		return m_open_files[fd]->path();
	}

	BAN::ErrorOr<BAN::RefPtr<Inode>> OpenFileDescriptorSet::inode_of(int fd)
	{
		TRY(validate_fd(fd));
		return m_open_files[fd]->inode();
	}

	BAN::ErrorOr<int> OpenFileDescriptorSet::flags_of(int fd) const
	{
		TRY(validate_fd(fd));
		return m_open_files[fd]->flags;
	}

	BAN::ErrorOr<void> OpenFileDescriptorSet::validate_fd(int fd) const
	{
		if (fd < 0 || fd >= (int)m_open_files.size())
			return BAN::Error::from_errno(EBADF);
		if (!m_open_files[fd])
			return BAN::Error::from_errno(EBADF);
		return {};
	}

	BAN::ErrorOr<int> OpenFileDescriptorSet::get_free_fd() const
	{
		for (int fd = 0; fd < (int)m_open_files.size(); fd++)
			if (!m_open_files[fd])
				return fd;
		return BAN::Error::from_errno(EMFILE);
	}

	BAN::ErrorOr<void> OpenFileDescriptorSet::get_free_fd_pair(int fds[2]) const
	{
		size_t found = 0;
		for (int fd = 0; fd < (int)m_open_files.size(); fd++)
		{
			if (!m_open_files[fd])
				fds[found++] = fd;
			if (found == 2)
				return {};
		}
		return BAN::Error::from_errno(EMFILE);
	}

}
