import { compiler } from "melodyc"; /* VERSION_PATTERN = r""" v? (?: (?:(?P[0-9]+)!)? # epoch (?P[0-9]+(?:\.[0-9]+)*) # release segment (?P
                                          # pre-release
            [-_\.]?
            (?P(a|b|c|rc|alpha|beta|pre|preview))
            [-_\.]?
            (?P[0-9]+)?
        )?
        (?P                                         # post release
            (?:-(?P[0-9]+))
            |
            (?:
                [-_\.]?
                (?Ppost|rev|r)
                [-_\.]?
                (?P[0-9]+)?
            )
        )?
        (?P                                          # dev release
            [-_\.]?
            (?Pdev)
            [-_\.]?
            (?P[0-9]+)?
        )?
    )
    (?:\+(?P[a-z0-9]+(?:[-_\.][a-z0-9]+)*))?       # local version
"""
*/

const VERSION_PATTERN = `
	;

	option of "v";

	match {
		option of match {
			capture epoch {
				some of ;
			}
			"!";
		}

		capture release {
			some of ;
			any of match {
				".";
				some of ;
			}
		}

		option of capture pre {
			option of either {
				".";
				"_";
				"-";
			}
			capture pre_l {
				either {
					"a";
					"b";
					"c";
					"rc";
					"alpha";
					"beta";
					"pre";
					"preview";
				}
			}
			option of either {
				".";
				"_";
				"-";
			}
			capture pre_n {
				some of ;
			}
		}

		option of capture post {
			either {
				match {
					"-";
					capture post_n {
						some of ;
					}
				}

				match {
					option of either {
						".";
						"_";
						"-";
					}
					capture post_l {
						either {
							"post";
							"rev";
							"r";
						}
					}
					option of either {
						".";
						"_";
						"-";
					}
					option of capture post_n {
						some of ;
					}
				}
			}
		}

		option of capture dev {
			option of either {
				".";
				"_";
				"-";
			}
			capture dev_l {
				"dev";
			}
			option of either {
				".";
				"_";
				"-";
			}
			option of capture dev_n {
				some of ;
			}
		}

		option of match {
			"+";
			capture local {
				some of either {
					a to z;
					0 to 9;
				}

				any of match {
					option of either {
						".";
						"_";
						"-";
					}
					some of either {
						a to z;
						0 to 9;
					}
				}
			}
		}
	}

	// get rid of random bullshit at the end
	capture random_bullshit {
		any of ;
	}

	;
`;

/*
 
	*/


const output = compiler(VERSION_PATTERN);
console.log(output);
const VERSION_REGEX = new RegExp(output);


export default class Pep440Version {
	epoch?: number;
	release: {
		major: number;
		minor: number;
		patch: number;
	};
	preRelease?: {
		name: string;
		version?: number;
	};
	postRelease?: {
		name?: string;
		version?: number;
	};
	dev?: {
		name: string;
		version?: number;
	};

	random_bullshit_at_the_end?: string;



	/**
		*
		* @param {string} versionString
		*/
	constructor(versionString: string) {
		console.log(`Constructing version from ${versionString}`);

		const match = versionString.match(VERSION_REGEX);

		if( match === null || match.groups === undefined ) {
			throw new Error("Couldn't parse version: " + versionString)
		}

		const g = match.groups;

		if( g['release'] === undefined ) {
			throw new Error("Couldn't parse version (because of the absence of a release): " + versionString)
		}

		const [ major, minor, patch ] = g['release'].split(".").map( e => Number(e) );

		this.release = {
			major: major ?? 0,
			minor: minor ?? 0,
			patch: patch ?? 0,
		}

		this.epoch = Number(g['epoch']);

		if( g['pre'] ) {
			this.preRelease = {
				name: g['pre_l'],
				version: g['pre_n'] ? Number(g['pre_n']) : undefined,
			}
		}

		if( g['post'] ) {
			this.postRelease = {
				name: g['post_l'],
				version: g['post_n'] ? Number(g['post_n']) : undefined,
			}
		}

		if( g['dev'] ) {
			this.dev = {
				name: g['dev_l'],
				version: g['dev_n'] ? Number(g['dev_n']) : undefined,
			}
		}

		this.random_bullshit_at_the_end = g['random_bullshit'];
	}

	/**
		* @returns the version (as as tring) in a format that can be correctly compared
		* even if it looks goofy
		*/
	toComparableString() {
		// comparing strings is annoying
		// for example: "1.2" > "1.12", even though 12 > 2
		//
		// To fix that issue, we're going to have fixed-length numbers,
		// so that comparing 1.2 and 1.12 would mean comparing
		// "000012" > "000002", this one being correct because 1 > 0.

		// pad to 8 because who in their right mind would use 8 digits for the major version
		// (noting that some people use 4 because they use years)
		const PAD_LENGTH = 8;
		
		const epoch = (this.epoch?.toString() ?? '0').padStart(PAD_LENGTH, '0');

		const release_major = (this.release.major.toString() ?? '0').padStart(PAD_LENGTH, '0');
		const release_minor = (this.release.minor.toString() ?? '0').padStart(PAD_LENGTH, '0');
		const release_patch = (this.release.patch.toString() ?? '0').padStart(PAD_LENGTH, '0');
		const release = `${release_major}.${release_minor}.${release_patch}`;

		const prename = (this.preRelease?.name || '').padStart(PAD_LENGTH, '0');
		const postname = (this.postRelease?.name || '').padStart(PAD_LENGTH, '0');
		const devname = (this.dev?.name || '').padStart(PAD_LENGTH, '0');


		const prever = (this.preRelease?.version?.toString() || '0').padStart(PAD_LENGTH, '0');
		const postver = (this.postRelease?.version?.toString() || '0').padStart(PAD_LENGTH, '0');
		const devver = (this.dev?.version?.toString() || '0').padStart(PAD_LENGTH, '0');

		return `${epoch}!${release}.${prename}${prever}.${postname}${postver}.${devname}${devver}`;
	}

	isAbove(other: Pep440Version) {
		return this.toComparableString() > other.toComparableString();
	}

	isBelow(other: Pep440Version) {
		return this.toComparableString() < other.toComparableString();
	}

	isEqual(other: Pep440Version) {
		return this.toComparableString() === other.toComparableString();
	}

	compare(other: Pep440Version) {
		if( this.isAbove(other) ) {
			return -1;
		}
		else if( this.isBelow(other) ) {
			return 1;
		}
		else {
			return 0;
		}
	}

	/**
		* @returns the version as a Pep440-compliant, human-readable string (e.g. "2.1.3")
		*/
	releaseString() {
		let str = "";

		if( this.epoch ) {
			str += `${this.epoch}!`;
		}
		str += this.release.major ?? 0;
		str += '.';
		str += this.release.minor ?? 0;
		str += '.';
		str += this.release.patch ?? 0;

		if(this.preRelease) {
			str += '.';
			if( this.preRelease.name ) {
				str += this.preRelease.name;
			}
			if( this.preRelease.version ) {
				str += this.preRelease.version;
			}
		}

		if(this.postRelease) {
			str += '.';
			if( this.postRelease.name ) {
				str += this.postRelease.name;
			}
			if( this.postRelease.version ) {
				str += this.postRelease.version;
			}
		}

		if(this.dev) {
			str += '.';
			if( this.dev.name ) {
				str += this.dev.name;
			}
			if( this.dev.version ) {
				str += this.dev.version;
			}
		}

		if( this.random_bullshit_at_the_end ) {
			str += this.random_bullshit_at_the_end;
		}

		return str;
	}

	//static fromString(...versionStrings: string[]) {
	//	return versionStrings.map( v => new Pep440Version(v) );
	//}
}

type OPERATOR_FUNCTION = (source: Pep440Version, target: Pep440Version) => boolean

type OPERATORS_FN_type = {
	[key: string]: OPERATOR_FUNCTION;
};

const OPERATORS_FUNCTIONS: OPERATORS_FN_type = {
	">=": (source, target) => target.isAbove(source) || target.isEqual(source),
	">": (source, target) => target.isAbove(source),
	"==": (source, target) => target.isEqual(source),
	"": (source, target) => true,
};

export class Pep440VersionRule {
	version: Pep440Version;
	operator: OPERATOR_FUNCTION;

	constructor(version: Pep440Version, operator: string = "") {
		this.version = version;
		this.operator = OPERATORS_FUNCTIONS[operator];
	}

	/**
		*/
	getSortedMatchingVersions(versions: Pep440Version[]) {
		const sorted = versions.sort( (a, b) => a.compare(b) );

		let filtered;
		if( typeof this.operator === "function" ) {
			filtered = sorted.filter( v => this.operator( this.version, v ) );
		}
		else {
			filtered = sorted;
		}


		return filtered;
	}

	static getRuleMatchingAnyVersion() {
		return new Pep440VersionRule(new Pep440Version("0.0.0"))
	}
}

//console.log( new Pep440Version("4!1.2.3dev2").toComparableString() )