#pragma once

#include "stm.hpp"

class Si5351C {
public:
	enum class PLL : uint8_t {
		A = 0,
		B = 1,
	};
	enum class DriveStrength : uint8_t {
		mA2 = 0x00,
		mA4 = 0x01,
		mA6 = 0x02,
		mA8 = 0x03,
	};
	enum class PLLSource : uint8_t {
		XTAL,
		CLKIN,
	};
	constexpr Si5351C(I2C_HandleTypeDef *i2c, uint32_t XTAL_freq, GPIO_InitTypeDef *intr_gpio = nullptr,
			uint16_t intr_pin = 0, GPIO_InitTypeDef *oeb_gpio = nullptr, uint16_t oeb_pin = 0):
		i2c(i2c),
		intr_gpio(intr_gpio),
		intr_pin(intr_pin),
		oeb_gpio(oeb_gpio),
		oeb_pin(oeb_pin),
		FreqPLL{},
		FreqXTAL(XTAL_freq),
		FreqCLKINDiv(0) {
	};
	bool Init(uint32_t clkin_freq = 0);
	bool ConfigureCLKIn(uint32_t clkin_freq);
	bool SetPLL(PLL pll, uint32_t frequency, PLLSource src, bool exactFrequency=true);
	bool SetCLK(uint8_t clknum, uint32_t frequency, PLL source, DriveStrength strength = DriveStrength::mA2, bool exactFrequency=true);
	bool SetBypass(uint8_t clknum, PLLSource source, DriveStrength strength = DriveStrength::mA2);
	bool SetCLKtoXTAL(uint8_t clknum);
	bool SetCLKToCLKIN(uint8_t clknum);
	bool Enable(uint8_t clknum);
	bool Disable(uint8_t clknum);
	bool Locked(PLL pll);
	bool WaitForLock(PLL pll, uint32_t timeout);
	bool ResetPLL(PLL pll);
	bool ExtCLKAvailable();

	// Direct register access of clk configuration registers
	// config has to point to a buffer containing at least 8 bytes
	bool WriteRawCLKConfig(uint8_t clknum, const uint8_t *config);
	bool ReadRawCLKConfig(uint8_t clknum, uint8_t *config);
private:
	void FindOptimalDivider(uint32_t f_pll, uint32_t f, uint32_t &P1, uint32_t &P2, uint32_t &P3, bool exact);
	enum class Reg : uint8_t {
		DeviceStatus = 0,
		InterruptStatusSticky = 1,
		InterruptStatusMask = 2,
		OutputEnableControl = 3,
		OEBPinMask = 9,
		PLLInputSource = 15,
		CLK0Control = 16,
		CLK1Control = 17,
		CLK2Control = 18,
		CLK3Control = 19,
		CLK4Control = 20,
		CLK5Control = 21,
		CLK6Control = 22,
		CLK7Control = 23,
		CLK3_0DisableState = 24,
		CLK7_4DisableState = 25,
		MSNA_CONFIG  = 26,
		MSNB_CONFIG = 34,
		MS0_CONFIG = 42,
		MS1_CONFIG = 50,
		MS2_CONFIG = 58,
		MS3_CONFIG = 66,
		MS4_CONFIG = 74,
		MS5_CONFIG = 82,
		MS6_CONFIG = 90,
		MS7_CONFIG = 91,
		R6_7_Divider = 92,
		// Left out: Spread Spectrum and VCXO parameters
		CLK0_Offset = 165,
		CLK1_Offset = 166,
		CLK2_Offset = 167,
		CLK3_Offset = 168,
		CLK4_Offset = 169,
		CLK5_Offset = 170,
		PLLReset = 177,
		CrystalLoadCapacitance = 183,
		FanoutEnable = 187,
	};

	using PLLConfig = struct {
		uint32_t P1, P2, P3;
		bool IntegerMode;
		PLLSource source;
	};
	bool WritePLLConfig(PLLConfig config, PLL pll);

	using ClkConfig = struct {
		uint32_t P1, P2, P3;
		uint8_t RDiv; // 1 to 128, only 2^n
		bool DivideBy4;
		bool PoweredDown;
		bool IntegerMode;
		PLL source;
		bool Inverted;
		DriveStrength strength;
	};
	bool WriteClkConfig(ClkConfig config, uint8_t clknum);

	static constexpr uint8_t address = 0xC0;
	bool WriteRegister(Reg reg, uint8_t data);
	bool ReadRegister(Reg reg, uint8_t *data);
	bool SetBits(Reg reg, uint8_t bits);
	bool ClearBits(Reg reg, uint8_t bits);
	bool WriteRegisterRange(Reg start, const uint8_t *data, uint8_t len);
	bool ReadRegisterRange(Reg start, uint8_t *data, uint8_t len);
	I2C_HandleTypeDef *i2c;
	GPIO_InitTypeDef *intr_gpio;
	uint16_t intr_pin;
	GPIO_InitTypeDef *oeb_gpio;
	uint16_t oeb_pin;
	uint32_t FreqPLL[2];
	uint32_t FreqXTAL, FreqCLKINDiv;
};
