//helper class to contain serialization meta data for a property, each property
//in a type tagged with a serialization annotation will contain an array of these
//objects each describing one property

import {
	IConstructable,
	SerializableType,
	InstantiationMethod,
} from "./interfaces";
import { NoOp } from "./string_transforms";

const TypeMap = new Map<any, Array<MetaData>>();

/** @internal */
export enum MetaDataFlag {
	DeserializePrimitive = 1 << 1,
	SerializePrimitive = 1 << 2,
	DeserializeArray = 1 << 3,
	SerializeArray = 1 << 4,
	DeserializeMap = 1 << 5,
	SerializeMap = 1 << 6,
	DeserializeJSON = 1 << 7,
	SerializeJSON = 1 << 8,
	DeserializeUsing = 1 << 11,
	SerializeUsing = 1 << 12,
	DeserializeObject = 1 << 13,
	SerializeObject = 1 << 14,

	AutoPrimitive = SerializePrimitive | DeserializePrimitive,
	AutoUsing = SerializeUsing | DeserializeUsing,
}

/** @internal */
export class MetaData {
	public keyName: string; //the key name of the property this meta data describes
	public transformKey = true;
	public serializedKey: string; //the target keyname for serializing
	public deserializedKey: string; //the target keyname for deserializing
	public serializedType: SerializableType<any>; //the type to use when serializing this property
	public deserializedType: SerializableType<any>; //the type to use when deserializing this property
	public flags: MetaDataFlag;

	constructor(keyName: string) {
		this.keyName = keyName;
		this.serializedKey = "";
		this.deserializedKey = "";
		this.deserializedType = Function;
		this.serializedType = Function;
		this.flags = 0;
	}

	public getSerializedKey(): string {
		if (this.serializedKey === this.keyName) {
			return MetaData.serializeKeyTransform(this.keyName);
		}
		return this.serializedKey ? this.serializedKey : this.keyName;
	}

	public getDeserializedKey(): string {
		if (this.deserializedKey === this.keyName) {
			return MetaData.deserializeKeyTransform(this.keyName);
		}
		return MetaData.deserializeKeyTransform(
			this.deserializedKey ? this.deserializedKey : this.keyName
		);
	}

	//checks for a key name in a meta data array
	public static hasKeyName(
		metadataArray: Array<MetaData>,
		key: string
	): boolean {
		for (var i = 0; i < metadataArray.length; i++) {
			if (metadataArray[i].keyName === key) return true;
		}
		return false;
	}

	//clone a meta data instance, used for inheriting serialization properties
	public static clone(data: MetaData): MetaData {
		const metadata = new MetaData(data.keyName);
		metadata.deserializedKey = data.deserializedKey;
		metadata.serializedKey = data.serializedKey;
		metadata.serializedType = data.serializedType;
		metadata.deserializedType = data.deserializedType;
		metadata.transformKey = data.transformKey;
		metadata.flags = data.flags;
		return metadata;
	}

	//gets meta data for a key name, creating a new meta data instance
	//if the input array doesn't already define one for the given keyName
	public static getMetaData(target: Function, keyName: string): MetaData {
		var metaDataList = TypeMap.get(target);

		if (metaDataList === void 0) {
			metaDataList = [];
			TypeMap.set(target, metaDataList);
		}

		for (var i = 0; i < metaDataList.length; i++) {
			if (metaDataList[i].keyName === keyName) {
				return metaDataList[i];
			}
		}
		metaDataList.push(new MetaData(keyName));
		return metaDataList[metaDataList.length - 1];
	}

	public static inheritMetaData(
		parentType: IConstructable,
		childType: IConstructable
	) {
		var parentMetaData: Array<MetaData> = TypeMap.get(parentType) || [];
		var childMetaData: Array<MetaData> = TypeMap.get(childType) || [];
		for (var i = 0; i < parentMetaData.length; i++) {
			const keyName = parentMetaData[i].keyName;
			if (!MetaData.hasKeyName(childMetaData, keyName)) {
				childMetaData.push(MetaData.clone(parentMetaData[i]));
			}
		}
		TypeMap.set(childType, childMetaData);
	}

	public static getMetaDataForType(type: IConstructable) {
		if (type !== null && type !== void 0) {
			return TypeMap.get(type) || null;
		}
		return null;
	}

	public static readonly TypeMap = TypeMap;

	public static serializeKeyTransform = NoOp;

	public static deserializeKeyTransform = NoOp;

	public static deserializeInstantationMethod = InstantiationMethod.New;
}
