import { Font as FontBase, parseFontFamily, genericFontFamilies, FontWeight, FontVariationSettings, FONTS_BASE_PATH } from './font-common';
import { FontStyleType, FontWeightType, FontVariationSettingsType } from './font-interfaces';
import { getNativeApp } from '../../application/helpers-common';
import { Trace } from '../../trace';
import { SDK_VERSION } from '../../utils/constants';
import * as fs from '../../file-system';

export * from './font-common';

const typefaceCache = new Map<string, android.graphics.Typeface>();
let appAssets: android.content.res.AssetManager;

export class Font extends FontBase {
	static default = new Font(undefined, undefined);

	private _typeface: android.graphics.Typeface;

	public withFontFamily(family: string): Font {
		return new Font(family, this.fontSize, this.fontStyle, this.fontWeight, 1, this.fontVariationSettings);
	}

	public withFontStyle(style: FontStyleType): Font {
		return new Font(this.fontFamily, this.fontSize, style, this.fontWeight, 1, this.fontVariationSettings);
	}

	public withFontWeight(weight: FontWeightType): Font {
		return new Font(this.fontFamily, this.fontSize, this.fontStyle, weight, 1, this.fontVariationSettings);
	}

	public withFontSize(size: number): Font {
		return new Font(this.fontFamily, size, this.fontStyle, this.fontWeight, 1, this.fontVariationSettings);
	}

	public withFontScale(scale: number): Font {
		return new Font(this.fontFamily, this.fontSize, this.fontStyle, this.fontWeight, 1, this.fontVariationSettings);
	}

	public withFontVariationSettings(variationSettings: Array<FontVariationSettingsType> | null): Font {
		return new Font(this.fontFamily, this.fontSize, this.fontStyle, this.fontWeight, 1, variationSettings);
	}

	getAndroidTypeface(): android.graphics.Typeface {
		if (!this._typeface) {
			this._typeface = SDK_VERSION >= 28 ? createTypeface(this) : createTypefaceLegacy(this);
		}

		return this._typeface;
	}

	getUIFont(defaultFont: any): any {
		return undefined;
	}
}

function computeFontCacheKey(fontFamily: string, font: Font) {
	const sep = ':';
	return [fontFamily, String(FontVariationSettings.toString(font.fontVariationSettings)).replace(/'/g, '').replace(/[\s,]/g, '_')].join(sep);
}

function loadFontFromFile(fontFamily: string, font: Font): android.graphics.Typeface {
	const cacheKey = SDK_VERSION >= 26 ? computeFontCacheKey(fontFamily, font) : fontFamily;

	appAssets = appAssets || getNativeApp<android.app.Application>().getApplicationContext().getAssets();
	if (!appAssets) {
		return null;
	}

	let result: android.graphics.Typeface;

	if (typefaceCache.has(cacheKey)) {
		result = typefaceCache.get(cacheKey);
	} else {
		const basePath = fs.path.join(fs.knownFolders.currentApp().path, FONTS_BASE_PATH, fontFamily);

		let fontAssetPath: string;

		if (fs.File.exists(basePath + '.ttf')) {
			fontAssetPath = basePath + '.ttf';
		} else if (fs.File.exists(basePath + '.otf')) {
			fontAssetPath = basePath + '.otf';
		} else {
			fontAssetPath = null;

			if (Trace.isEnabled()) {
				Trace.write('Could not find font file for ' + fontFamily, Trace.categories.Error, Trace.messageType.error);
			}
		}

		result = null; // Default

		if (fontAssetPath) {
			try {
				if (SDK_VERSION >= 26) {
					const builder = new android.graphics.Typeface.Builder(fontAssetPath);
					if (builder) {
						builder.setFontVariationSettings(font.fontVariationSettings?.length ? FontVariationSettings.toString(font.fontVariationSettings) : '');
						result = builder.build();
					} else {
						result = android.graphics.Typeface.createFromFile(fontAssetPath);
						if (Trace.isEnabled()) {
							Trace.write('Could not create builder for ' + fontFamily, Trace.categories.Error, Trace.messageType.error);
						}
					}
				} else {
					result = android.graphics.Typeface.createFromFile(fontAssetPath);
				}
			} catch (e) {
				if (Trace.isEnabled()) {
					Trace.write('Error loading font asset: ' + fontAssetPath, Trace.categories.Error, Trace.messageType.error);
				}
			}
		}

		// The value will be null if there has already been an attempt to load the font but failed
		typefaceCache.set(cacheKey, result);
	}

	return result;
}

function createTypeface(font: Font): android.graphics.Typeface {
	const fontFamilies = parseFontFamily(font.fontFamily);
	const fontWeightNum = getNumericFontWeight(font.fontWeight);

	let result: android.graphics.Typeface;

	for (const fontFamily of fontFamilies) {
		switch (fontFamily.toLowerCase()) {
			case genericFontFamilies.serif:
				result = android.graphics.Typeface.create(android.graphics.Typeface.SERIF, fontWeightNum, font.isItalic);
				break;
			case genericFontFamilies.sansSerif:
			case genericFontFamilies.system:
				result = android.graphics.Typeface.create(android.graphics.Typeface.SANS_SERIF, fontWeightNum, font.isItalic);
				break;
			case genericFontFamilies.monospace:
				result = android.graphics.Typeface.create(android.graphics.Typeface.MONOSPACE, fontWeightNum, font.isItalic);
				break;
			default: {
				result = loadFontFromFile(fontFamily, font);
				if (result) {
					result = android.graphics.Typeface.create(result, fontWeightNum, font.isItalic);
				}
				break;
			}
		}

		// Found the font!
		if (result) {
			break;
		}
	}

	if (!result) {
		result = android.graphics.Typeface.create(android.graphics.Typeface.SANS_SERIF, fontWeightNum, font.isItalic);
	}

	return result;
}

function createTypefaceLegacy(font: Font): android.graphics.Typeface {
	const fontFamilies = parseFontFamily(font.fontFamily);
	const fontWeight = font.fontWeight;
	// https://stackoverflow.com/questions/19691530/valid-values-for-androidfontfamily-and-what-they-map-to
	const fontSuffix = getFontWeightSuffix(fontWeight);

	let result: android.graphics.Typeface;
	let fontStyle: number = 0;

	if (font.isBold) {
		fontStyle |= android.graphics.Typeface.BOLD;
	}
	if (font.isItalic) {
		fontStyle |= android.graphics.Typeface.ITALIC;
	}

	for (const fontFamily of fontFamilies) {
		switch (fontFamily.toLowerCase()) {
			case genericFontFamilies.serif:
				result = android.graphics.Typeface.create('serif' + fontSuffix, fontStyle);
				break;
			case genericFontFamilies.sansSerif:
			case genericFontFamilies.system:
				result = android.graphics.Typeface.create('sans-serif' + fontSuffix, fontStyle);
				break;
			case genericFontFamilies.monospace:
				result = android.graphics.Typeface.create('monospace' + fontSuffix, fontStyle);
				break;
			default: {
				result = loadFontFromFile(fontFamily, font);
				if (result && fontStyle) {
					result = android.graphics.Typeface.create(result, fontStyle);
				}
				break;
			}
		}

		// Found the font!
		if (result) {
			break;
		}
	}

	if (!result) {
		result = android.graphics.Typeface.create('sans-serif' + fontSuffix, fontStyle);
	}

	return result;
}

function getFontWeightSuffix(fontWeight: FontWeightType): string {
	if (typeof fontWeight === 'number') {
		fontWeight = (fontWeight + '') as any;
	}
	switch (fontWeight) {
		case FontWeight.THIN:
			return SDK_VERSION >= 16 ? '-thin' : '';
		case FontWeight.EXTRA_LIGHT:
		case FontWeight.LIGHT:
			return SDK_VERSION >= 16 ? '-light' : '';
		case FontWeight.NORMAL:
		case '400':
		case undefined:
		case null:
			return '';
		case FontWeight.MEDIUM:
		case FontWeight.SEMI_BOLD:
			return SDK_VERSION >= 21 ? '-medium' : '';
		case FontWeight.BOLD:
		case '700':
		case FontWeight.EXTRA_BOLD:
			return '';
		case FontWeight.BLACK:
			return SDK_VERSION >= 21 ? '-black' : '';
		default:
			throw new Error(`Invalid font weight: "${fontWeight}"`);
	}
}

function getNumericFontWeight(fontWeight: FontWeightType): number {
	let value: number;

	if (typeof fontWeight === 'number') {
		value = fontWeight;
	} else {
		switch (fontWeight) {
			case FontWeight.NORMAL:
			case undefined:
			case null:
				value = 400;
				break;
			case FontWeight.BOLD:
				value = 700;
				break;
			default:
				value = parseInt(fontWeight);
				break;
		}
	}

	return value;
}
