/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

'use strict';

const path = require('path');
const fs = require('fs');
const https = require('https');
const url = require('url');
const minimatch = require('minimatch');

// list of languagesId not shipped with VSCode. The information is used to associate an icon with a language association
// Please try and keep this list in alphabetical order! Thank you.
const nonBuiltInLanguages = { // { fileNames, extensions  }
	"argdown": { extensions: ['ad', 'adown', 'argdown', 'argdn'] },
	"bicep": { extensions: ['bicep'] },
	"elixir": { extensions: ['ex'] },
	"elm": { extensions: ['elm'] },
	"erb": { extensions: ['erb', 'rhtml', 'html.erb'] },
	"github-issues": { extensions: ['github-issues'] },
	"gradle": { extensions: ['gradle'] },
	"godot": { extensions: ['gd', 'godot', 'tres', 'tscn'] },
	"haml": { extensions: ['haml'] },
	"haskell": { extensions: ['hs'] },
	"haxe": { extensions: ['hx'] },
	"jinja": { extensions: ['jinja'] },
	"kotlin": { extensions: ['kt'] },
	"mustache": { extensions: ['mustache', 'mst', 'mu', 'stache'] },
	"nunjucks": { extensions: ['nunjucks', 'nunjs', 'nunj', 'nj', 'njk', 'tmpl', 'tpl'] },
	"ocaml": { extensions: ['ml', 'mli', 'mll', 'mly', 'eliom', 'eliomi'] },
	"puppet": { extensions: ['puppet'] },
	"r": { extensions: ['r', 'rhistory', 'rprofile', 'rt'] },
	"rescript": { extensions: ['res', 'resi'] },
	"sass": { extensions: ['sass'] },
	"stylus": { extensions: ['styl'] },
	"terraform": { extensions: ['tf', 'tfvars', 'hcl'] },
	"todo": { fileNames: ['todo'] },
	"vala": { extensions: ['vala'] },
	"vue": { extensions: ['vue'] }
};

// list of languagesId that inherit the icon from another language
const inheritIconFromLanguage = {
	"jsonc": 'json',
	"jsonl": 'json',
	"postcss": 'css',
	"django-html": 'html',
	"blade": 'php'
};

const ignoreExtAssociation = {
	"properties": true
};

const FROM_DISK = true; // set to true to take content from a repo checked out next to the vscode repo

let font, fontMappingsFile, fileAssociationFile, colorsFile;
if (!FROM_DISK) {
	font = 'https://raw.githubusercontent.com/jesseweed/seti-ui/master/styles/_fonts/seti/seti.woff';
	fontMappingsFile = 'https://raw.githubusercontent.com/jesseweed/seti-ui/master/styles/_fonts/seti.less';
	fileAssociationFile = 'https://raw.githubusercontent.com/jesseweed/seti-ui/master/styles/components/icons/mapping.less';
	colorsFile = 'https://raw.githubusercontent.com/jesseweed/seti-ui/master/styles/ui-variables.less';
} else {
	font = '../../../seti-ui/styles/_fonts/seti/seti.woff';
	fontMappingsFile = '../../../seti-ui/styles/_fonts/seti.less';
	fileAssociationFile = '../../../seti-ui/styles/components/icons/mapping.less';
	colorsFile = '../../../seti-ui/styles/ui-variables.less';
}

function getCommitSha(repoId) {
	const commitInfo = 'https://api.github.com/repos/' + repoId + '/commits/master';
	return download(commitInfo).then(function (content) {
		try {
			const lastCommit = JSON.parse(content);
			return Promise.resolve({
				commitSha: lastCommit.sha,
				commitDate: lastCommit.commit.author.date
			});
		} catch (e) {
			console.error('Failed parsing ' + content);
			return Promise.resolve(null);
		}
	}, function () {
		console.error('Failed loading ' + commitInfo);
		return Promise.resolve(null);
	});
}

function download(source) {
	if (source.startsWith('.')) {
		return readFile(source);
	}
	return new Promise((c, e) => {
		const _url = url.parse(source);
		const options = { host: _url.host, port: _url.port, path: _url.path, headers: { 'User-Agent': 'NodeJS' } };
		let content = '';
		https.get(options, function (response) {
			response.on('data', function (data) {
				content += data.toString();
			}).on('end', function () {
				c(content);
			});
		}).on('error', function (err) {
			e(err.message);
		});
	});
}

function readFile(fileName) {
	return new Promise((c, e) => {
		fs.readFile(fileName, function (err, data) {
			if (err) {
				e(err);
			} else {
				c(data.toString());
			}
		});
	});
}

function downloadBinary(source, dest) {
	if (source.startsWith('.')) {
		return copyFile(source, dest);
	}

	return new Promise((c, e) => {
		https.get(source, function (response) {
			switch (response.statusCode) {
				case 200: {
					const file = fs.createWriteStream(dest);
					response.on('data', function (chunk) {
						file.write(chunk);
					}).on('end', function () {
						file.end();
						c(null);
					}).on('error', function (err) {
						fs.unlink(dest);
						e(err.message);
					});
					break;
				}
				case 301:
				case 302:
				case 303:
				case 307:
					console.log('redirect to ' + response.headers.location);
					downloadBinary(response.headers.location, dest).then(c, e);
					break;
				default:
					e(new Error('Server responded with status code ' + response.statusCode));
			}
		});
	});
}

function copyFile(fileName, dest) {
	return new Promise((c, e) => {
		let cbCalled = false;
		function handleError(err) {
			if (!cbCalled) {
				e(err);
				cbCalled = true;
			}
		}
		const rd = fs.createReadStream(fileName);
		rd.on("error", handleError);
		const wr = fs.createWriteStream(dest);
		wr.on("error", handleError);
		wr.on("close", function () {
			if (!cbCalled) {
				c();
				cbCalled = true;
			}
		});
		rd.pipe(wr);
	});
}

function darkenColor(color) {
	let res = '#';
	for (let i = 1; i < 7; i += 2) {
		const newVal = Math.round(parseInt('0x' + color.substr(i, 2), 16) * 0.9);
		const hex = newVal.toString(16);
		if (hex.length === 1) {
			res += '0';
		}
		res += hex;
	}
	return res;
}

function mergeMapping(to, from, property) {
	if (from[property]) {
		if (to[property]) {
			to[property].push(...from[property]);
		} else {
			to[property] = from[property];
		}
	}
}

function getLanguageMappings() {
	const langMappings = {};
	const allExtensions = fs.readdirSync('..');
	for (let i = 0; i < allExtensions.length; i++) {
		const dirPath = path.join('..', allExtensions[i], 'package.json');
		if (fs.existsSync(dirPath)) {
			const content = fs.readFileSync(dirPath).toString();
			const jsonContent = JSON.parse(content);
			const languages = jsonContent.contributes && jsonContent.contributes.languages;
			if (Array.isArray(languages)) {
				for (let k = 0; k < languages.length; k++) {
					const languageId = languages[k].id;
					if (languageId) {
						const extensions = languages[k].extensions;
						const mapping = {};
						if (Array.isArray(extensions)) {
							mapping.extensions = extensions.map(function (e) { return e.substr(1).toLowerCase(); });
						}
						const filenames = languages[k].filenames;
						if (Array.isArray(filenames)) {
							mapping.fileNames = filenames.map(function (f) { return f.toLowerCase(); });
						}
						const filenamePatterns = languages[k].filenamePatterns;
						if (Array.isArray(filenamePatterns)) {
							mapping.filenamePatterns = filenamePatterns.map(function (f) { return f.toLowerCase(); });
						}
						const existing = langMappings[languageId];

						if (existing) {
							// multiple contributions to the same language
							// give preference to the contribution wth the configuration
							if (languages[k].configuration) {
								mergeMapping(mapping, existing, 'extensions');
								mergeMapping(mapping, existing, 'fileNames');
								mergeMapping(mapping, existing, 'filenamePatterns');
								langMappings[languageId] = mapping;
							} else {
								mergeMapping(existing, mapping, 'extensions');
								mergeMapping(existing, mapping, 'fileNames');
								mergeMapping(existing, mapping, 'filenamePatterns');
							}
						} else {
							langMappings[languageId] = mapping;
						}
					}
				}
			}
		}
	}
	for (const languageId in nonBuiltInLanguages) {
		langMappings[languageId] = nonBuiltInLanguages[languageId];
	}
	return langMappings;
}

exports.copyFont = function () {
	return downloadBinary(font, './icons/seti.woff');
};

exports.update = function () {

	console.log('Reading from ' + fontMappingsFile);
	const def2Content = {};
	const ext2Def = {};
	const fileName2Def = {};
	const def2ColorId = {};
	const colorId2Value = {};
	const lang2Def = {};

	function writeFileIconContent(info) {
		const iconDefinitions = {};
		const allDefs = Object.keys(def2Content).sort();

		for (let i = 0; i < allDefs.length; i++) {
			const def = allDefs[i];
			const entry = { fontCharacter: def2Content[def] };
			const colorId = def2ColorId[def];
			if (colorId) {
				const colorValue = colorId2Value[colorId];
				if (colorValue) {
					entry.fontColor = colorValue;

					const entryInverse = { fontCharacter: entry.fontCharacter, fontColor: darkenColor(colorValue) };
					iconDefinitions[def + '_light'] = entryInverse;
				}
			}
			iconDefinitions[def] = entry;
		}

		function getInvertSet(input) {
			const result = {};
			for (const assoc in input) {
				const invertDef = input[assoc] + '_light';
				if (iconDefinitions[invertDef]) {
					result[assoc] = invertDef;
				}
			}
			return result;
		}

		const res = {
			information_for_contributors: [
				'This file has been generated from data in https://github.com/jesseweed/seti-ui',
				'- icon definitions: https://github.com/jesseweed/seti-ui/blob/master/styles/_fonts/seti.less',
				'- icon colors: https://github.com/jesseweed/seti-ui/blob/master/styles/ui-variables.less',
				'- file associations: https://github.com/jesseweed/seti-ui/blob/master/styles/components/icons/mapping.less',
				'If you want to provide a fix or improvement, please create a pull request against the jesseweed/seti-ui repository.',
				'Once accepted there, we are happy to receive an update request.',
			],
			fonts: [{
				id: "seti",
				src: [{ "path": "./seti.woff", "format": "woff" }],
				weight: "normal",
				style: "normal",
				size: "150%"
			}],
			iconDefinitions: iconDefinitions,
			//	folder: "_folder",
			file: "_default",
			fileExtensions: ext2Def,
			fileNames: fileName2Def,
			languageIds: lang2Def,
			light: {
				file: "_default_light",
				fileExtensions: getInvertSet(ext2Def),
				languageIds: getInvertSet(lang2Def),
				fileNames: getInvertSet(fileName2Def)
			},
			version: 'https://github.com/jesseweed/seti-ui/commit/' + info.commitSha,
		};

		const path = './icons/vs-seti-icon-theme.json';
		fs.writeFileSync(path, JSON.stringify(res, null, '\t'));
		console.log('written ' + path);
	}


	let match;

	return download(fontMappingsFile).then(function (content) {
		const regex = /@([\w-]+):\s*'(\\E[0-9A-F]+)';/g;
		const contents = {};
		while ((match = regex.exec(content)) !== null) {
			contents[match[1]] = match[2];
		}

		return download(fileAssociationFile).then(function (content) {
			const regex2 = /\.icon-(?:set|partial)\(['"]([\w-\.+]+)['"],\s*['"]([\w-]+)['"],\s*(@[\w-]+)\)/g;
			while ((match = regex2.exec(content)) !== null) {
				const pattern = match[1];
				let def = '_' + match[2];
				const colorId = match[3];
				let storedColorId = def2ColorId[def];
				let i = 1;
				while (storedColorId && colorId !== storedColorId) { // different colors for the same def?
					def = `_${match[2]}_${i}`;
					storedColorId = def2ColorId[def];
					i++;
				}
				if (!def2ColorId[def]) {
					def2ColorId[def] = colorId;
					def2Content[def] = contents[match[2]];
				}

				if (def === '_default') {
					continue; // no need to assign default color.
				}
				if (pattern[0] === '.') {
					ext2Def[pattern.substr(1).toLowerCase()] = def;
				} else {
					fileName2Def[pattern.toLowerCase()] = def;
				}
			}
			// replace extensions for languageId
			const langMappings = getLanguageMappings();
			for (let lang in langMappings) {
				const mappings = langMappings[lang];
				const exts = mappings.extensions || [];
				const fileNames = mappings.fileNames || [];
				const filenamePatterns = mappings.filenamePatterns || [];
				let preferredDef = null;
				// use the first file extension association for the preferred definition
				for (let i1 = 0; i1 < exts.length && !preferredDef; i1++) {
					preferredDef = ext2Def[exts[i1]];
				}
				// use the first file name association for the preferred definition, if not availbale
				for (let i1 = 0; i1 < fileNames.length && !preferredDef; i1++) {
					preferredDef = fileName2Def[fileNames[i1]];
				}
				for (let i1 = 0; i1 < filenamePatterns.length && !preferredDef; i1++) {
					let pattern = filenamePatterns[i1];
					for (const name in fileName2Def) {
						if (minimatch(name, pattern)) {
							preferredDef = fileName2Def[name];
							break;
						}
					}
				}
				if (preferredDef) {
					lang2Def[lang] = preferredDef;
					if (!nonBuiltInLanguages[lang] && !inheritIconFromLanguage[lang]) {
						for (let i2 = 0; i2 < exts.length; i2++) {
							// remove the extension association, unless it is different from the preferred
							if (ext2Def[exts[i2]] === preferredDef || ignoreExtAssociation[exts[i2]]) {
								delete ext2Def[exts[i2]];
							}
						}
						for (let i2 = 0; i2 < fileNames.length; i2++) {
							// remove the fileName association, unless it is different from the preferred
							if (fileName2Def[fileNames[i2]] === preferredDef) {
								delete fileName2Def[fileNames[i2]];
							}
						}
						for (let i2 = 0; i2 < filenamePatterns.length; i2++) {
							let pattern = filenamePatterns[i2];
							// remove the filenamePatterns association, unless it is different from the preferred
							for (const name in fileName2Def) {
								if (minimatch(name, pattern) && fileName2Def[name] === preferredDef) {
									delete fileName2Def[name];
								}
							}
						}
					}
				}
			}
			for (const lang in inheritIconFromLanguage) {
				const superLang = inheritIconFromLanguage[lang];
				const def = lang2Def[superLang];
				if (def) {
					lang2Def[lang] = def;
				} else {
					console.log('skipping icon def for ' + lang + ': no icon for ' + superLang + ' defined');
				}

			}


			return download(colorsFile).then(function (content) {
				const regex3 = /(@[\w-]+):\s*(#[0-9a-z]+)/g;
				while ((match = regex3.exec(content)) !== null) {
					colorId2Value[match[1]] = match[2];
				}
				return getCommitSha('jesseweed/seti-ui').then(function (info) {
					try {
						writeFileIconContent(info);

						const cgmanifestPath = './cgmanifest.json';
						const cgmanifest = fs.readFileSync(cgmanifestPath).toString();
						const cgmanifestContent = JSON.parse(cgmanifest);
						cgmanifestContent['registrations'][0]['component']['git']['commitHash'] = info.commitSha;
						fs.writeFileSync(cgmanifestPath, JSON.stringify(cgmanifestContent, null, '\t'));
						console.log('updated ' + cgmanifestPath);

						console.log('Updated to jesseweed/seti-ui@' + info.commitSha.substr(0, 7) + ' (' + info.commitDate.substr(0, 10) + ')');

					} catch (e) {
						console.error(e);
					}
				});
			});
		});
	}, console.error);
};

if (path.basename(process.argv[1]) === 'update-icon-theme.js') {
	exports.copyFont().then(() => exports.update());
}



