/*
	This file is part of Fennix Drivers.

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

#if defined(__amd64__) || defined(__i386__)

#include "aip.hpp"

#include <driver.hpp>
#include <interface/aip.h>
#include <interface/input.h>

namespace Driver::AdvancedIntegratedPeripheral
{
	extern dev_t DriverID;
	dev_t MouseDevID = -1;
	bool PacketReady = false;
	bool FourPackets = false;
	bool MouseButton45 = false;
	uint8_t Cycle = 0;
	PS2_MOUSE_PACKET Packet = {};

	InputReport mir = {};
	void PS2MouseInterruptHandler(CPU::TrapFrame *)
	{
		uint8_t data = v0::PS2ReadData(DriverID);
		if (data == PS2_MOUSE_RESP_ACK ||
			data == PS2_MOUSE_RESP_RESEND)
			return;

		if (!PacketReady)
		{
			switch (Cycle)
			{
			case 0:
			{
				if ((data & 0b00001000 /* Always 1 */) == 0)
					return;

				Packet.Base.Raw = data;
				Cycle++;
				break;
			}
			case 1:
			{
				Packet.XMovement = data;
				Cycle++;
				break;
			}
			case 2:
			{
				Packet.YMovement = data;
				if (FourPackets)
					Cycle++;
				else
				{
					Cycle = 0;
					PacketReady = true;
				}
				break;
			}
			case 3:
			{
				Packet.ZMovement.Raw = data;
				Cycle = 0;
				PacketReady = true;
				break;
			}
			default:
				break;
			}
			return;
		}

/* https://stackoverflow.com/a/3208376/9352057 */
#define BYTE_TO_BINARY_PATTERN "%c%c%c%c%c%c%c%c"
#define BYTE_TO_BINARY(byte)         \
	((byte) & 0x80 ? '1' : '0'),     \
		((byte) & 0x40 ? '1' : '0'), \
		((byte) & 0x20 ? '1' : '0'), \
		((byte) & 0x10 ? '1' : '0'), \
		((byte) & 0x08 ? '1' : '0'), \
		((byte) & 0x04 ? '1' : '0'), \
		((byte) & 0x02 ? '1' : '0'), \
		((byte) & 0x01 ? '1' : '0')

		debug("PS/2 Mouse Packet: [" BYTE_TO_BINARY_PATTERN ":" BYTE_TO_BINARY_PATTERN ":" BYTE_TO_BINARY_PATTERN ":" BYTE_TO_BINARY_PATTERN "] LB:%d RB:%d MB:%d A1:%d XS:%d YS:%d XO:%d YO:%d | X:%03d Y:%03d | Z:%d B4:%d B5:%d A0:%d A0:%d",
			  BYTE_TO_BINARY(Packet.Base.Raw),
			  BYTE_TO_BINARY(Packet.XMovement),
			  BYTE_TO_BINARY(Packet.YMovement),
			  BYTE_TO_BINARY(Packet.ZMovement.Raw),
			  Packet.Base.LeftButton, Packet.Base.RightButton, Packet.Base.MiddleButton,
			  Packet.Base.Always1,
			  Packet.Base.XSign, Packet.Base.YSign,
			  Packet.Base.XOverflow, Packet.Base.YOverflow,
			  Packet.XMovement, Packet.YMovement,
			  Packet.ZMovement.Z, Packet.ZMovement.Button4, Packet.ZMovement.Button5,
			  Packet.ZMovement.Always0, Packet.ZMovement.Always0_2);

		int X, Y;
		X = Packet.XMovement - (Packet.Base.XSign ? 256 : 0);
		Y = Packet.YMovement - (Packet.Base.YSign ? 256 : 0);

		if (Packet.Base.XOverflow)
			X = 0;

		if (Packet.Base.YOverflow)
			Y = 0;

		mir.Type = INPUT_TYPE_MOUSE;
		mir.Device = MouseDevID;
		mir.Mouse.LeftButton = Packet.Base.LeftButton;
		mir.Mouse.RightButton = Packet.Base.RightButton;
		mir.Mouse.MiddleButton = Packet.Base.MiddleButton;
		mir.Mouse.Button4 = Packet.ZMovement.Button4;
		mir.Mouse.Button5 = Packet.ZMovement.Button5;
		mir.Mouse.X = X;
		mir.Mouse.Y = -Y;
		mir.Mouse.Z = Packet.ZMovement.Z;
		v0::ReportInputEvent(DriverID, &mir);
		PacketReady = false;
	}

	void MouseSampleRate(uint8_t SampleRate)
	{
		v0::PS2WriteCommand(DriverID, PS2_CMD_WRITE_NEXT_BYTE_TO_PS2_PORT_2_INPUT);
		v0::PS2WriteData(DriverID, PS2_MOUSE_CMD_SET_SAMPLE_RATE);
		v0::PS2ReadData(DriverID);

		v0::PS2WriteCommand(DriverID, PS2_CMD_WRITE_NEXT_BYTE_TO_PS2_PORT_2_INPUT);
		v0::PS2WriteData(DriverID, SampleRate);
		v0::PS2ReadData(DriverID);
	}

	int __fs_ms_Ioctl(struct Inode *, unsigned long, void *)
	{
		return 0;
	}

	const struct InodeOperations MouseOps = {
		.Lookup = nullptr,
		.Create = nullptr,
		.Remove = nullptr,
		.Rename = nullptr,
		.Read = nullptr,
		.Write = nullptr,
		.Truncate = nullptr,
		.Open = nullptr,
		.Close = nullptr,
		.Ioctl = __fs_ms_Ioctl,
		.ReadDir = nullptr,
		.MkDir = nullptr,
		.RmDir = nullptr,
		.SymLink = nullptr,
		.ReadLink = nullptr,
		.Seek = nullptr,
		.Stat = nullptr,
	};

	int InitializeMouse()
	{
		v0::PS2WriteData(DriverID, PS2_CMD_WRITE_NEXT_BYTE_TO_PS2_PORT_2_INPUT);
		v0::PS2WriteData(DriverID, PS2_MOUSE_CMD_RESET);
		uint8_t test = v0::PS2ReadData(DriverID);
		if (test != PS2_MOUSE_RESP_TEST_PASSED &&
			test != PS2_MOUSE_RESP_ACK)
		{
			trace("PS/2 mouse reset failed! (%#x)", test);
			return -EFAULT;
		}

		v0::RegisterInterruptHandler(DriverID, 12, (void *)PS2MouseInterruptHandler);

		MouseDevID = v0::RegisterDevice(DriverID, INPUT_TYPE_MOUSE, &MouseOps);

		v0::PS2WriteCommand(DriverID, PS2_CMD_WRITE_NEXT_BYTE_TO_PS2_PORT_2_INPUT);
		v0::PS2WriteData(DriverID, PS2_MOUSE_CMD_SET_DEFAULTS);
		v0::PS2ReadData(DriverID);

		v0::PS2WriteCommand(DriverID, PS2_CMD_WRITE_NEXT_BYTE_TO_PS2_PORT_2_INPUT);
		v0::PS2WriteData(DriverID, PS2_MOUSE_CMD_ENABLE_DATA_REPORTING);

		MouseSampleRate(200);
		MouseSampleRate(100);
		MouseSampleRate(80);

		v0::PS2WriteCommand(DriverID, PS2_CMD_WRITE_NEXT_BYTE_TO_PS2_PORT_2_INPUT);
		v0::PS2WriteData(DriverID, PS2_MOUSE_CMD_READ_ID);
		uint8_t Device2ID = v0::PS2ReadData(DriverID);
		trace("PS/2 Mouse ID: %#x", Device2ID);

		MouseSampleRate(200);
		MouseSampleRate(200);
		MouseSampleRate(80);

		v0::PS2WriteCommand(DriverID, PS2_CMD_WRITE_NEXT_BYTE_TO_PS2_PORT_2_INPUT);
		v0::PS2WriteData(DriverID, PS2_MOUSE_CMD_READ_ID);
		Device2ID = v0::PS2ReadData(DriverID);
		trace("PS/2 Mouse ID: %#x", Device2ID);

		if (Device2ID >= 3 && Device2ID <= 4)
			FourPackets = true;
		if (Device2ID == 4)
			MouseButton45 = true;

		return 0;
	}

	int FinalizeMouse()
	{
		v0::PS2WriteCommand(DriverID, PS2_CMD_WRITE_NEXT_BYTE_TO_PS2_PORT_2_INPUT);
		v0::PS2WriteData(DriverID, PS2_MOUSE_CMD_DISABLE_DATA_REPORTING);

		v0::UnregisterDevice(DriverID, MouseDevID);
		return 0;
	}

	int DetectPS2Mouse()
	{
		v0::PS2WriteCommand(DriverID, PS2_CMD_WRITE_NEXT_BYTE_TO_PS2_PORT_2_INPUT);
		v0::PS2WriteData(DriverID, PS2_MOUSE_CMD_DISABLE_DATA_REPORTING);
		if (v0::PS2ACKTimeout(DriverID) != 0)
			trace("PS/2 mouse failed to disable data reporting!");

		v0::PS2WriteCommand(DriverID, PS2_CMD_WRITE_NEXT_BYTE_TO_PS2_PORT_2_INPUT);
		v0::PS2WriteData(DriverID, PS2_MOUSE_CMD_READ_ID);
		if (v0::PS2ACKTimeout(DriverID) != 0)
			trace("PS/2 mouse failed to read ID!");

		uint8_t recByte;
		int timeout = 1000000;
		while (timeout--)
		{
			recByte = v0::PS2ReadData(DriverID);
			if (recByte != PS2_ACK)
				break;
		}
		Device2ID[0] = recByte;

		timeout = 1000000;
		while (timeout--)
		{
			recByte = v0::PS2ReadData(DriverID);
			if (recByte != PS2_ACK)
				break;
		}
		Device2ID[1] = recByte;

		trace("PS2 Mouse Device: 0x%X 0x%X", Device2ID[0], Device2ID[1]);
		return 0;
	}
}

#endif