import SVGAngleTypeEnum from './SVGAngleTypeEnum.js';
import * as PropertySymbol from '../PropertySymbol.js';
import BrowserWindow from '../window/BrowserWindow.js';

const ATTRIBUTE_REGEXP = /^(\d+|\d+\.\d+)(deg|rad|grad|turn|)$/;

/**
 * SVG angle.
 *
 * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGAngle
 */
export default class SVGAngle {
	// Static properties
	public static SVG_ANGLETYPE_UNKNOWN = SVGAngleTypeEnum.unknown;
	public static SVG_ANGLETYPE_UNSPECIFIED = SVGAngleTypeEnum.unspecified;
	public static SVG_ANGLETYPE_DEG = SVGAngleTypeEnum.deg;
	public static SVG_ANGLETYPE_RAD = SVGAngleTypeEnum.rad;
	public static SVG_ANGLETYPE_GRAD = SVGAngleTypeEnum.grad;

	// Internal properties
	public [PropertySymbol.window]: BrowserWindow;
	public [PropertySymbol.getAttribute]: () => string | null = null;
	public [PropertySymbol.setAttribute]: (value: string) => void | null = null;
	public [PropertySymbol.attributeValue]: string = '';
	public [PropertySymbol.readOnly]: boolean = false;

	/**
	 * Constructor.
	 *
	 * @param illegalConstructorSymbol Illegal constructor symbol.
	 * @param window Window.
	 * @param [options] Options.
	 * @param [options.readOnly] Read only.
	 * @param [options.getAttribute] Get attribute.
	 * @param [options.setAttribute] Set attribute.
	 */
	constructor(
		illegalConstructorSymbol: symbol,
		window: BrowserWindow,
		options?: {
			readOnly?: boolean;
			getAttribute?: () => string | null;
			setAttribute?: (value: string) => void;
		}
	) {
		if (illegalConstructorSymbol !== PropertySymbol.illegalConstructor) {
			throw new TypeError('Illegal constructor');
		}

		this[PropertySymbol.window] = window;

		if (options) {
			this[PropertySymbol.readOnly] = !!options.readOnly;
			this[PropertySymbol.getAttribute] = options.getAttribute || null;
			this[PropertySymbol.setAttribute] = options.setAttribute || null;
		}
	}

	/**
	 * Returns unit type.
	 *
	 * @returns Unit type.
	 */
	public get unitType(): SVGAngleTypeEnum {
		const attributeValue = this[PropertySymbol.getAttribute]
			? this[PropertySymbol.getAttribute]()
			: this[PropertySymbol.attributeValue];
		const match = attributeValue?.match(ATTRIBUTE_REGEXP);

		if (!match) {
			return SVGAngleTypeEnum.unknown;
		}

		if (isNaN(parseFloat(match[1]))) {
			return SVGAngleTypeEnum.unknown;
		}

		switch (match[2]) {
			case '':
				return SVGAngleTypeEnum.unspecified;
			case 'deg':
				return SVGAngleTypeEnum.deg;
			case 'rad':
				return SVGAngleTypeEnum.rad;
			case 'grad':
				return SVGAngleTypeEnum.grad;
			case 'turn':
				return SVGAngleTypeEnum.unknown;
			default:
				return SVGAngleTypeEnum.unspecified;
		}
	}

	/**
	 * Returns value.
	 *
	 * @returns Value.
	 */
	public get value(): number {
		const attributeValue = this[PropertySymbol.getAttribute]
			? this[PropertySymbol.getAttribute]()
			: this[PropertySymbol.attributeValue];
		const match = attributeValue?.match(ATTRIBUTE_REGEXP);

		if (!match) {
			return 0;
		}

		const parsedValue = parseFloat(match[1]);

		if (isNaN(parsedValue)) {
			return 0;
		}

		switch (match[2]) {
			case '':
				return parsedValue;
			case 'deg':
				return parsedValue;
			case 'rad':
				return parsedValue * (180 / Math.PI);
			case 'grad':
				return parsedValue * (180 / 200);
			case 'turn':
				return parsedValue * 360;
			default:
				return parsedValue;
		}
	}

	/**
	 * Sets value.
	 *
	 * @param value Value in pixels.
	 */
	public set value(value: number) {
		if (this[PropertySymbol.readOnly]) {
			throw new this[PropertySymbol.window].TypeError(
				`Failed to set the 'value' property on 'SVGAngle': The object is read-only.`
			);
		}

		// Value in pixels
		value = typeof value !== 'number' ? parseFloat(String(value)) : value;
		if (isNaN(value)) {
			throw new this[PropertySymbol.window].TypeError(
				`Failed to set the 'value' property on 'SVGAngle': The provided float value is non-finite.`
			);
		}

		let unitType = '';
		let valueInSpecifiedUnits = value;

		switch (this.unitType) {
			case SVGAngleTypeEnum.unspecified:
				valueInSpecifiedUnits = value;
				unitType = '';
				break;
			case SVGAngleTypeEnum.deg:
				valueInSpecifiedUnits = value;
				unitType = 'deg';
				break;
			case SVGAngleTypeEnum.rad:
				valueInSpecifiedUnits = value / (180 / Math.PI);
				unitType = 'rad';
				break;
			case SVGAngleTypeEnum.grad:
				valueInSpecifiedUnits = value / (180 / 200);
				unitType = 'grad';
				break;
			case SVGAngleTypeEnum.unknown:
				valueInSpecifiedUnits = value / 360;
				unitType = 'turn';
			default:
				break;
		}

		this[PropertySymbol.attributeValue] = String(valueInSpecifiedUnits) + unitType;

		if (this[PropertySymbol.setAttribute]) {
			this[PropertySymbol.setAttribute](this[PropertySymbol.attributeValue]);
		}
	}

	/**
	 * Returns value as string.
	 *
	 * @returns Value as string.
	 */
	public get valueAsString(): string {
		return this[PropertySymbol.getAttribute]
			? this[PropertySymbol.getAttribute]() || '0'
			: this[PropertySymbol.attributeValue] || '0';
	}

	/**
	 * Returns value in specified units.
	 *
	 * @returns Value in specified units.
	 */
	public get valueInSpecifiedUnits(): number {
		const attributeValue = this.valueAsString;
		return parseFloat(attributeValue) || 0;
	}

	/**
	 * New value specific units.
	 * @param unitType
	 * @param value
	 */
	public newValueSpecifiedUnits(unitType: number, value: number): void {
		if (this[PropertySymbol.readOnly]) {
			throw new this[PropertySymbol.window].TypeError(
				`Failed to execute 'newValueSpecifiedUnits' on 'SVGAngle': The object is read-only.`
			);
		}

		if (typeof unitType !== 'number') {
			throw new this[PropertySymbol.window].TypeError(
				`Failed to execute 'newValueSpecifiedUnits' on 'SVGAngle': parameter 1 ('unitType') is not of type 'number'.`
			);
		}

		value = typeof value !== 'number' ? parseFloat(value) : value;

		if (isNaN(value)) {
			throw new this[PropertySymbol.window].TypeError(
				`Failed to execute 'newValueSpecifiedUnits' on 'SVGAngle': The provided float value is non-finite.`
			);
		}

		let unit = '';

		switch (unitType) {
			case SVGAngleTypeEnum.unspecified:
				unit = '';
				break;
			case SVGAngleTypeEnum.deg:
				unit = 'deg';
				break;
			case SVGAngleTypeEnum.rad:
				unit = 'rad';
				break;
			case SVGAngleTypeEnum.grad:
				unit = 'grad';
				break;
			case SVGAngleTypeEnum.unknown:
				unit = 'turn';
				break;
			default:
				break;
		}

		this[PropertySymbol.attributeValue] = String(value) + unit;

		if (this[PropertySymbol.setAttribute]) {
			this[PropertySymbol.setAttribute](this[PropertySymbol.attributeValue]);
		}
	}

	/**
	 * Convert to specific units.
	 * @param unitType
	 */
	public convertToSpecifiedUnits(unitType: SVGAngleTypeEnum): void {
		if (this[PropertySymbol.readOnly]) {
			throw new this[PropertySymbol.window].TypeError(
				`Failed to execute 'convertToSpecifiedUnits' on 'SVGAngle': The object is read-only.`
			);
		}

		if (typeof unitType !== 'number') {
			throw new this[PropertySymbol.window].TypeError(
				`Failed to execute 'convertToSpecifiedUnits' on 'SVGAngle': parameter 1 ('unitType') is not of type 'number'.`
			);
		}

		let value = this.value;
		let unit = '';

		switch (unitType) {
			case SVGAngleTypeEnum.unspecified:
				unit = '';
				break;
			case SVGAngleTypeEnum.deg:
				unit = 'deg';
				break;
			case SVGAngleTypeEnum.rad:
				unit = 'rad';
				value = value / (180 / Math.PI);
				break;
			case SVGAngleTypeEnum.grad:
				unit = 'grad';
				value = value / (180 / 200);
				break;
			case SVGAngleTypeEnum.unknown:
				unit = 'turn';
				value = value / 360;
				break;
			default:
				break;
		}

		this[PropertySymbol.attributeValue] = String(value) + unit;

		if (this[PropertySymbol.setAttribute]) {
			this[PropertySymbol.setAttribute](this[PropertySymbol.attributeValue]);
		}
	}
}