protected tranlateExpress(type: Type, strExp: string): MatchExpress {
        let expresses: ((MatchExpress) | string)[] = [];

        let idxOr = strExp.indexOf('||');
        let idxAd = strExp.indexOf('&&');
        if (idxAd < 0 && idxOr < 0) {
            expresses.push(this.expressToFunc(type, this.spiltBrace(strExp)))
        } else {
            if (idxOr > idxAd) {
                let leftExp = this.spiltBrace(strExp.substring(0, idxOr));
                if (leftExp) {
                    expresses.push(this.tranlateExpress(type, leftExp));
                }
                let rightExp = this.spiltBrace(strExp.substring(idxOr + 2));
                if (rightExp) {
                    expresses.push('||');
                    expresses.push(this.tranlateExpress(type, rightExp));
                }
            } else if (idxAd > idxOr) {
                let leftExp = this.spiltBrace(strExp.substring(0, idxAd));
                if (leftExp) {
                    expresses.push(this.tranlateExpress(type, leftExp));
                }
                let rightExp = this.spiltBrace(strExp.substring(idxAd + 2));
                if (rightExp) {
                    expresses.push('&&');
                    expresses.push(this.tranlateExpress(type, rightExp));
                }
            }
        }

        return this.mergeExpress(...expresses);
    }


protected mergeExpress(...expresses: (MatchExpress | string)[]): MatchExpress {
        return (method: string, fullName: string, targetType?: Type, pointcut?: IPointcut) => {
            let flag;
            expresses.forEach((express, idx) => {
                if (isDefined(flag)) {
                    return;
                }
                if (isFunction(express)) {
                    let rel = express(method, fullName, targetType, pointcut);
                    if (idx < expresses.length - 2) {
                        if (!rel && express[idx + 1] === '&&') {
                            flag = false;
                        }
                        if (rel && express[idx + 1] === '||') {
                            flag = true;
                        }
                    } else {
                        flag = rel;
                    }
                }

            });
            return flag;
        }
    }


() => this.update()


ch => {
			this.callOnModel.push(this.editor.addTypingListener(ch, this.trigger.bind(this, ch)));
		}


(e: editorCommon.IModelContentChangedEvent) => {
			if (e.changeType === editorCommon.EventType.ModelRawContentChangedFlush) {
				// a model.setValue() was called
				canceled = true;
			} else if (e.changeType === editorCommon.EventType.ModelRawContentChangedLineChanged) {
				var changedLine = (<editorCommon.IModelContentChangedLineChangedEvent>e).lineNumber;
				canceled = changedLine <= position.lineNumber;

			} else if (e.changeType === editorCommon.EventType.ModelRawContentChangedLinesInserted) {
				var insertLine = (<editorCommon.IModelContentChangedLinesInsertedEvent>e).fromLineNumber;
				canceled = insertLine <= position.lineNumber;

			} else if (e.changeType === editorCommon.EventType.ModelRawContentChangedLinesDeleted) {
				var deleteLine2 = (<editorCommon.IModelContentChangedLinesDeletedEvent>e).toLineNumber;
				canceled = deleteLine2 <= position.lineNumber;
			}

			if (canceled) {
				// cancel only once
				unbind.dispose();
			}
		}


edits => {
			return this.workerService.computeMoreMinimalEdits(model.uri, edits, []);
		}


edits => {

			unbind.dispose();

			if (canceled || isFalsyOrEmpty(edits)) {
				return;
			}

			this.editor.executeCommand(this.getId(), new EditOperationsCommand(edits, this.editor.getSelection()));

		}


(err) => {
			unbind.dispose();
			throw err;
		}


edits => workerService.computeMoreMinimalEdits(editor.getModel().uri, edits, editor.getSelections())


edits => {
			if (!state.validate(editor) || isFalsyOrEmpty(edits)) {
				return;
			}
			const command = new EditOperationsCommand(edits, editor.getSelection());
			editor.executeCommand(this.id, command);
			editor.focus();
		}


accessor => {
	const editor = accessor.get(ICodeEditorService).getFocusedCodeEditor();
	if (editor) {
		return new class extends AbstractFormatAction {
			constructor() {
				super(<any>{});
			}
			_getFormattingEdits(editor: editorCommon.ICommonCodeEditor): TPromise<editorCommon.ISingleEditOperation[]> {
				const model = editor.getModel();
				const editorSelection = editor.getSelection();
				const {tabSize, insertSpaces } = model.getOptions();

				return editorSelection.isEmpty()
					? getDocumentFormattingEdits(model, { tabSize, insertSpaces })
					: getDocumentRangeFormattingEdits(model, editorSelection, { tabSize, insertSpaces });
			}
		}().run(accessor, editor);
	}
}


@commonEditorContribution
class FormatOnType implements editorCommon.IEditorContribution {

	private static ID = 'editor.contrib.autoFormat';

	private editor: editorCommon.ICommonCodeEditor;
	private workerService: IEditorWorkerService;
	private callOnDispose: IDisposable[];
	private callOnModel: IDisposable[];

	constructor(editor: editorCommon.ICommonCodeEditor, @IEditorWorkerService workerService: IEditorWorkerService) {
		this.editor = editor;
		this.workerService = workerService;
		this.callOnDispose = [];
		this.callOnModel = [];

		this.callOnDispose.push(editor.onDidChangeConfiguration(() => this.update()));
		this.callOnDispose.push(editor.onDidChangeModel(() => this.update()));
		this.callOnDispose.push(editor.onDidChangeModelMode(() => this.update()));
		this.callOnDispose.push(OnTypeFormattingEditProviderRegistry.onDidChange(this.update, this));
	}

	private update(): void {

		// clean up
		this.callOnModel = dispose(this.callOnModel);

		// we are disabled
		if (!this.editor.getConfiguration().contribInfo.formatOnType) {
			return;
		}

		// no model
		if (!this.editor.getModel()) {
			return;
		}

		var model = this.editor.getModel();

		// no support
		var [support] = OnTypeFormattingEditProviderRegistry.ordered(model);
		if (!support || !support.autoFormatTriggerCharacters) {
			return;
		}

		// register typing listeners that will trigger the format
		support.autoFormatTriggerCharacters.forEach(ch => {
			this.callOnModel.push(this.editor.addTypingListener(ch, this.trigger.bind(this, ch)));
		});
	}

	private trigger(ch: string): void {

		if (this.editor.getSelections().length > 1) {
			return;
		}

		var model = this.editor.getModel(),
			position = this.editor.getPosition(),
			canceled = false;

		// install a listener that checks if edits happens before the
		// position on which we format right now. If so, we won't
		// apply the format edits
		var unbind = this.editor.onDidChangeModelRawContent((e: editorCommon.IModelContentChangedEvent) => {
			if (e.changeType === editorCommon.EventType.ModelRawContentChangedFlush) {
				// a model.setValue() was called
				canceled = true;
			} else if (e.changeType === editorCommon.EventType.ModelRawContentChangedLineChanged) {
				var changedLine = (<editorCommon.IModelContentChangedLineChangedEvent>e).lineNumber;
				canceled = changedLine <= position.lineNumber;

			} else if (e.changeType === editorCommon.EventType.ModelRawContentChangedLinesInserted) {
				var insertLine = (<editorCommon.IModelContentChangedLinesInsertedEvent>e).fromLineNumber;
				canceled = insertLine <= position.lineNumber;

			} else if (e.changeType === editorCommon.EventType.ModelRawContentChangedLinesDeleted) {
				var deleteLine2 = (<editorCommon.IModelContentChangedLinesDeletedEvent>e).toLineNumber;
				canceled = deleteLine2 <= position.lineNumber;
			}

			if (canceled) {
				// cancel only once
				unbind.dispose();
			}
		});

		let modelOpts = model.getOptions();

		getOnTypeFormattingEdits(model, position, ch, {
			tabSize: modelOpts.tabSize,
			insertSpaces: modelOpts.insertSpaces
		}).then(edits => {
			return this.workerService.computeMoreMinimalEdits(model.uri, edits, []);
		}).then(edits => {

			unbind.dispose();

			if (canceled || isFalsyOrEmpty(edits)) {
				return;
			}

			this.editor.executeCommand(this.getId(), new EditOperationsCommand(edits, this.editor.getSelection()));

		}, (err) => {
			unbind.dispose();
			throw err;
		});
	}

	public getId(): string {
		return FormatOnType.ID;
	}

	public dispose(): void {
		this.callOnDispose = dispose(this.callOnDispose);
		this.callOnModel = dispose(this.callOnModel);
	}
}


export abstract class AbstractFormatAction extends EditorAction {

	public run(accessor: ServicesAccessor, editor: editorCommon.ICommonCodeEditor): TPromise<void> {

		const workerService = accessor.get(IEditorWorkerService);

		const formattingPromise = this._getFormattingEdits(editor);
		if (!formattingPromise) {
			return TPromise.as(void 0);
		}

		// Capture the state of the editor
		const state = editor.captureState(editorCommon.CodeEditorStateFlag.Value, editorCommon.CodeEditorStateFlag.Position);

		// Receive formatted value from worker
		return formattingPromise.then(edits => workerService.computeMoreMinimalEdits(editor.getModel().uri, edits, editor.getSelections())).then(edits => {
			if (!state.validate(editor) || isFalsyOrEmpty(edits)) {
				return;
			}
			const command = new EditOperationsCommand(edits, editor.getSelection());
			editor.executeCommand(this.id, command);
			editor.focus();
		});
	}

	protected abstract _getFormattingEdits(editor: editorCommon.ICommonCodeEditor): TPromise<editorCommon.ISingleEditOperation[]>;
}


@editorAction
export class FormatDocumentAction extends AbstractFormatAction {

	constructor() {
		super({
			id: 'editor.action.formatDocument',
			label: nls.localize('formatDocument.label', "Format Document"),
			alias: 'Format Document',
			precondition: ContextKeyExpr.and(EditorContextKeys.Writable, ModeContextKeys.hasDocumentFormattingProvider),
			kbOpts: {
				kbExpr: EditorContextKeys.TextFocus,
				primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_F,
				// secondary: [KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_D)],
				linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_I }
			},
			menuOpts: {
				group: '1_modification',
				order: 1.3
			}
		});
	}

	protected _getFormattingEdits(editor: editorCommon.ICommonCodeEditor): TPromise<editorCommon.ISingleEditOperation[]> {
		const model = editor.getModel();
		const { tabSize, insertSpaces} = model.getOptions();
		return getDocumentFormattingEdits(model, { tabSize, insertSpaces });
	}
}


@editorAction
export class FormatSelectionAction extends AbstractFormatAction {

	constructor() {
		super({
			id: 'editor.action.formatSelection',
			label: nls.localize('formatSelection.label', "Format Selection"),
			alias: 'Format Code',
			precondition: ContextKeyExpr.and(EditorContextKeys.Writable, ModeContextKeys.hasDocumentFormattingProvider, EditorContextKeys.HasNonEmptySelection),
			kbOpts: {
				kbExpr: EditorContextKeys.TextFocus,
				primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_F)
			},
			menuOpts: {
				group: '1_modification',
				order: 1.31
			}
		});
	}

	protected _getFormattingEdits(editor: editorCommon.ICommonCodeEditor): TPromise<editorCommon.ISingleEditOperation[]> {
		const model = editor.getModel();
		const { tabSize, insertSpaces} = model.getOptions();
		return getDocumentRangeFormattingEdits(model, editor.getSelection(), { tabSize, insertSpaces });
	}
}


private update(): void {

		// clean up
		this.callOnModel = dispose(this.callOnModel);

		// we are disabled
		if (!this.editor.getConfiguration().contribInfo.formatOnType) {
			return;
		}

		// no model
		if (!this.editor.getModel()) {
			return;
		}

		var model = this.editor.getModel();

		// no support
		var [support] = OnTypeFormattingEditProviderRegistry.ordered(model);
		if (!support || !support.autoFormatTriggerCharacters) {
			return;
		}

		// register typing listeners that will trigger the format
		support.autoFormatTriggerCharacters.forEach(ch => {
			this.callOnModel.push(this.editor.addTypingListener(ch, this.trigger.bind(this, ch)));
		});
	}


private trigger(ch: string): void {

		if (this.editor.getSelections().length > 1) {
			return;
		}

		var model = this.editor.getModel(),
			position = this.editor.getPosition(),
			canceled = false;

		// install a listener that checks if edits happens before the
		// position on which we format right now. If so, we won't
		// apply the format edits
		var unbind = this.editor.onDidChangeModelRawContent((e: editorCommon.IModelContentChangedEvent) => {
			if (e.changeType === editorCommon.EventType.ModelRawContentChangedFlush) {
				// a model.setValue() was called
				canceled = true;
			} else if (e.changeType === editorCommon.EventType.ModelRawContentChangedLineChanged) {
				var changedLine = (<editorCommon.IModelContentChangedLineChangedEvent>e).lineNumber;
				canceled = changedLine <= position.lineNumber;

			} else if (e.changeType === editorCommon.EventType.ModelRawContentChangedLinesInserted) {
				var insertLine = (<editorCommon.IModelContentChangedLinesInsertedEvent>e).fromLineNumber;
				canceled = insertLine <= position.lineNumber;

			} else if (e.changeType === editorCommon.EventType.ModelRawContentChangedLinesDeleted) {
				var deleteLine2 = (<editorCommon.IModelContentChangedLinesDeletedEvent>e).toLineNumber;
				canceled = deleteLine2 <= position.lineNumber;
			}

			if (canceled) {
				// cancel only once
				unbind.dispose();
			}
		});

		let modelOpts = model.getOptions();

		getOnTypeFormattingEdits(model, position, ch, {
			tabSize: modelOpts.tabSize,
			insertSpaces: modelOpts.insertSpaces
		}).then(edits => {
			return this.workerService.computeMoreMinimalEdits(model.uri, edits, []);
		}).then(edits => {

			unbind.dispose();

			if (canceled || isFalsyOrEmpty(edits)) {
				return;
			}

			this.editor.executeCommand(this.getId(), new EditOperationsCommand(edits, this.editor.getSelection()));

		}, (err) => {
			unbind.dispose();
			throw err;
		});
	}


public getId(): string {
		return FormatOnType.ID;
	}


public dispose(): void {
		this.callOnDispose = dispose(this.callOnDispose);
		this.callOnModel = dispose(this.callOnModel);
	}


public run(accessor: ServicesAccessor, editor: editorCommon.ICommonCodeEditor): TPromise<void> {

		const workerService = accessor.get(IEditorWorkerService);

		const formattingPromise = this._getFormattingEdits(editor);
		if (!formattingPromise) {
			return TPromise.as(void 0);
		}

		// Capture the state of the editor
		const state = editor.captureState(editorCommon.CodeEditorStateFlag.Value, editorCommon.CodeEditorStateFlag.Position);

		// Receive formatted value from worker
		return formattingPromise.then(edits => workerService.computeMoreMinimalEdits(editor.getModel().uri, edits, editor.getSelections())).then(edits => {
			if (!state.validate(editor) || isFalsyOrEmpty(edits)) {
				return;
			}
			const command = new EditOperationsCommand(edits, editor.getSelection());
			editor.executeCommand(this.id, command);
			editor.focus();
		});
	}


protected abstract _getFormattingEdits(editor: editorCommon.ICommonCodeEditor): TPromise<editorCommon.ISingleEditOperation[]>;


protected _getFormattingEdits(editor: editorCommon.ICommonCodeEditor): TPromise<editorCommon.ISingleEditOperation[]> {
		const model = editor.getModel();
		const { tabSize, insertSpaces} = model.getOptions();
		return getDocumentFormattingEdits(model, { tabSize, insertSpaces });
	}


protected _getFormattingEdits(editor: editorCommon.ICommonCodeEditor): TPromise<editorCommon.ISingleEditOperation[]> {
		const model = editor.getModel();
		const { tabSize, insertSpaces} = model.getOptions();
		return getDocumentRangeFormattingEdits(model, editor.getSelection(), { tabSize, insertSpaces });
	}


_getFormattingEdits(editor: editorCommon.ICommonCodeEditor): TPromise<editorCommon.ISingleEditOperation[]> {
				const model = editor.getModel();
				const editorSelection = editor.getSelection();
				const {tabSize, insertSpaces } = model.getOptions();

				return editorSelection.isEmpty()
					? getDocumentFormattingEdits(model, { tabSize, insertSpaces })
					: getDocumentRangeFormattingEdits(model, editorSelection, { tabSize, insertSpaces });
			}


(o, item) => {
      o[item.column_name] = item.column_type.match(/enum\((.*)\)/)[1].split(',').map((item: string) => item.match(/'(.*)'/)![1]);
      return o;
    }


(item: string) => item.match(/'(.*)'/)![1]


col => ({
      name: col.column_name,
      type: col.data_type,
      maxLength: col.length,
      defaultValue: col.column_default,
      nullable: col.is_nullable === 'YES',
      primary: col.column_key === 'PRI',
      unique: col.column_key === 'UNI',
    })


index => ({
      columnName: index.Column_name,
      keyName: index.Key_name,
      unique: !index.Non_unique,
      primary: index.Key_name === 'PRIMARY',
    })


export class MySqlSchemaHelper extends SchemaHelper {

  static readonly TYPES = {
    boolean: ['tinyint(1)', 'tinyint'],
    number: ['int(?)', 'int', 'float', 'double', 'tinyint', 'smallint'],
    float: ['float'],
    double: ['double'],
    tinyint: ['tinyint'],
    smallint: ['smallint'],
    Date: ['datetime(?)', 'timestamp(?)', 'datetime', 'timestamp'],
    date: ['datetime(?)', 'timestamp(?)', 'datetime', 'timestamp'],
    string: ['varchar(?)', 'varchar', 'text', 'bigint', 'enum'],
    text: ['text'],
    object: ['json'],
    json: ['json'],
    enum: ['enum'],
  };

  static readonly DEFAULT_TYPE_LENGTHS = {
    number: 11,
    string: 255,
    date: 0,
  };

  static readonly DEFAULT_VALUES = {
    'now()': ['now()', 'current_timestamp'],
    'current_timestamp(?)': ['current_timestamp(?)'],
    '0': ['0', 'false'],
  };

  getSchemaBeginning(charset: string): string {
    return `set names ${charset};\nset foreign_key_checks = 0;\n\n`;
  }

  getSchemaEnd(): string {
    return 'set foreign_key_checks = 1;\n';
  }

  finalizeTable(table: CreateTableBuilder, charset: string): void {
    table.engine('InnoDB');
    table.charset(charset);
  }

  getTypeDefinition(prop: EntityProperty): string {
    return super.getTypeDefinition(prop, MySqlSchemaHelper.TYPES, MySqlSchemaHelper.DEFAULT_TYPE_LENGTHS);
  }

  getTypeFromDefinition(type: string, defaultType: string): string {
    return super.getTypeFromDefinition(type, defaultType, MySqlSchemaHelper.TYPES);
  }

  getListTablesSQL(): string {
    return `select table_name as table_name from information_schema.tables where table_type = 'BASE TABLE' and table_schema = schema()`;
  }

  getRenameColumnSQL(tableName: string, from: Column, to: EntityProperty, idx = 0): string {
    const type = `${to.columnTypes[idx]}${to.unsigned ? ' unsigned' : ''} ${to.nullable ? 'null' : 'not null'}${to.default ? ' default ' + to.default : ''}`;
    return `alter table \`${tableName}\` change \`${from.name}\` \`${to.fieldNames[idx]}\` ${type}`;
  }

  getForeignKeysSQL(tableName: string, schemaName?: string): string {
    return `select distinct k.constraint_name, k.column_name, k.referenced_table_name, k.referenced_column_name, c.update_rule, c.delete_rule `
      + `from information_schema.key_column_usage k `
      + `inner join information_schema.referential_constraints c on c.constraint_name = k.constraint_name and c.table_name = '${tableName}' `
      + `where k.table_name = '${tableName}' and k.table_schema = database() and c.constraint_schema = database() and k.referenced_column_name is not null`;
  }

  async getEnumDefinitions(connection: AbstractSqlConnection, tableName: string, schemaName?: string): Promise<Dictionary> {
    const sql =  `select column_name as column_name, column_type as column_type from information_schema.columns
      where data_type = 'enum' and table_name = '${tableName}'`;
    const enums = await connection.execute<any[]>(sql);

    return enums.reduce((o, item) => {
      o[item.column_name] = item.column_type.match(/enum\((.*)\)/)[1].split(',').map((item: string) => item.match(/'(.*)'/)![1]);
      return o;
    }, {} as Dictionary<string>);
  }

  async getColumns(connection: AbstractSqlConnection, tableName: string, schemaName?: string): Promise<any[]> {
    const sql = `select column_name as column_name, column_default as column_default, is_nullable as is_nullable, data_type as data_type, column_key as column_key, ifnull(datetime_precision, character_maximum_length) length
      from information_schema.columns where table_schema = database() and table_name = '${tableName}'`;
    const columns = await connection.execute<any[]>(sql);

    return columns.map(col => ({
      name: col.column_name,
      type: col.data_type,
      maxLength: col.length,
      defaultValue: col.column_default,
      nullable: col.is_nullable === 'YES',
      primary: col.column_key === 'PRI',
      unique: col.column_key === 'UNI',
    }));
  }

  async getIndexes(connection: AbstractSqlConnection, tableName: string, schemaName?: string): Promise<Index[]> {
    const sql = `show index from \`${tableName}\``;
    const indexes = await connection.execute<any[]>(sql);

    return indexes.map(index => ({
      columnName: index.Column_name,
      keyName: index.Key_name,
      unique: !index.Non_unique,
      primary: index.Key_name === 'PRIMARY',
    }));
  }

  isSame(prop: EntityProperty, column: Column, idx?: number): IsSame {
    return super.isSame(prop, column, idx, MySqlSchemaHelper.TYPES, MySqlSchemaHelper.DEFAULT_VALUES);
  }

  normalizeDefaultValue(defaultValue: string, length: number) {
    return super.normalizeDefaultValue(defaultValue, length, MySqlSchemaHelper.DEFAULT_VALUES);
  }

}


getSchemaBeginning(charset: string): string {
    return `set names ${charset};\nset foreign_key_checks = 0;\n\n`;
  }


getSchemaEnd(): string {
    return 'set foreign_key_checks = 1;\n';
  }


finalizeTable(table: CreateTableBuilder, charset: string): void {
    table.engine('InnoDB');
    table.charset(charset);
  }


getTypeDefinition(prop: EntityProperty): string {
    return super.getTypeDefinition(prop, MySqlSchemaHelper.TYPES, MySqlSchemaHelper.DEFAULT_TYPE_LENGTHS);
  }


getTypeFromDefinition(type: string, defaultType: string): string {
    return super.getTypeFromDefinition(type, defaultType, MySqlSchemaHelper.TYPES);
  }


getListTablesSQL(): string {
    return `select table_name as table_name from information_schema.tables where table_type = 'BASE TABLE' and table_schema = schema()`;
  }


getRenameColumnSQL(tableName: string, from: Column, to: EntityProperty, idx = 0): string {
    const type = `${to.columnTypes[idx]}${to.unsigned ? ' unsigned' : ''} ${to.nullable ? 'null' : 'not null'}${to.default ? ' default ' + to.default : ''}`;
    return `alter table \`${tableName}\` change \`${from.name}\` \`${to.fieldNames[idx]}\` ${type}`;
  }


getForeignKeysSQL(tableName: string, schemaName?: string): string {
    return `select distinct k.constraint_name, k.column_name, k.referenced_table_name, k.referenced_column_name, c.update_rule, c.delete_rule `
      + `from information_schema.key_column_usage k `
      + `inner join information_schema.referential_constraints c on c.constraint_name = k.constraint_name and c.table_name = '${tableName}' `
      + `where k.table_name = '${tableName}' and k.table_schema = database() and c.constraint_schema = database() and k.referenced_column_name is not null`;
  }


async getEnumDefinitions(connection: AbstractSqlConnection, tableName: string, schemaName?: string): Promise<Dictionary> {
    const sql =  `select column_name as column_name, column_type as column_type from information_schema.columns
      where data_type = 'enum' and table_name = '${tableName}'`;
    const enums = await connection.execute<any[]>(sql);

    return enums.reduce((o, item) => {
      o[item.column_name] = item.column_type.match(/enum\((.*)\)/)[1].split(',').map((item: string) => item.match(/'(.*)'/)![1]);
      return o;
    }, {} as Dictionary<string>);
  }


async getColumns(connection: AbstractSqlConnection, tableName: string, schemaName?: string): Promise<any[]> {
    const sql = `select column_name as column_name, column_default as column_default, is_nullable as is_nullable, data_type as data_type, column_key as column_key, ifnull(datetime_precision, character_maximum_length) length
      from information_schema.columns where table_schema = database() and table_name = '${tableName}'`;
    const columns = await connection.execute<any[]>(sql);

    return columns.map(col => ({
      name: col.column_name,
      type: col.data_type,
      maxLength: col.length,
      defaultValue: col.column_default,
      nullable: col.is_nullable === 'YES',
      primary: col.column_key === 'PRI',
      unique: col.column_key === 'UNI',
    }));
  }


async getIndexes(connection: AbstractSqlConnection, tableName: string, schemaName?: string): Promise<Index[]> {
    const sql = `show index from \`${tableName}\``;
    const indexes = await connection.execute<any[]>(sql);

    return indexes.map(index => ({
      columnName: index.Column_name,
      keyName: index.Key_name,
      unique: !index.Non_unique,
      primary: index.Key_name === 'PRIMARY',
    }));
  }


isSame(prop: EntityProperty, column: Column, idx?: number): IsSame {
    return super.isSame(prop, column, idx, MySqlSchemaHelper.TYPES, MySqlSchemaHelper.DEFAULT_VALUES);
  }


normalizeDefaultValue(defaultValue: string, length: number) {
    return super.normalizeDefaultValue(defaultValue, length, MySqlSchemaHelper.DEFAULT_VALUES);
  }


() => {
    cy.loadConfig({}, "applicationConfigFilled.json", { confirmationId: "123" })
    cy.visit(route)
  }


({
  beforeAllExtend,
  afterAllExtend,
  afterEachExtend,
  beforeEachExtend,
}: {
  beforeAllExtend?: () => Promise<void> | void;
  afterAllExtend?: () => Promise<void> | void;
  afterEachExtend?: () => Promise<void> | void;
  beforeEachExtend?: () => Promise<void> | void;
} = {}) => {
  let dateNowSpy: SpyInstance;
  let mongoConnection: Mongoose;

  beforeAll(async () => {
    // @ts-ignore
    mongoConnection = await connectDB(global.__MONGO_URI__);

    dateNowSpy = jest
      .spyOn(Date, 'now')
      .mockImplementation(() => 1570032448857);

    if (beforeAllExtend) {
      await beforeAllExtend();
    }
  }, 10000);

  beforeEach(async () => {
    if (beforeEachExtend) {
      await beforeEachExtend();
    }
  }, 10000);

  afterEach(async () => {
    if (afterEachExtend) {
      await afterEachExtend();
    }

    await Promise.all(
      mongoConnection.connections.map(collection => collection.dropDatabase()),
    );
  }, 10000);

  afterAll(async () => {
    dateNowSpy.mockRestore();

    if (afterAllExtend) {
      await afterAllExtend();
    }

    await mongoConnection.disconnect();
  }, 10000);
}


async () => {
    // @ts-ignore
    mongoConnection = await connectDB(global.__MONGO_URI__);

    dateNowSpy = jest
      .spyOn(Date, 'now')
      .mockImplementation(() => 1570032448857);

    if (beforeAllExtend) {
      await beforeAllExtend();
    }
  }


() => 1570032448857


async () => {
    if (beforeEachExtend) {
      await beforeEachExtend();
    }
  }


async () => {
    if (afterEachExtend) {
      await afterEachExtend();
    }

    await Promise.all(
      mongoConnection.connections.map(collection => collection.dropDatabase()),
    );
  }


collection => collection.dropDatabase()


async () => {
    dateNowSpy.mockRestore();

    if (afterAllExtend) {
      await afterAllExtend();
    }

    await mongoConnection.disconnect();
  }


/** BasePicker component. */
export interface IBasePicker<T> {
  /** Gets the current value of the input. */
  items: T[] | undefined;

  /** Sets focus to the focus zone. */
  focus: () => void;

  /** Set focus to the input */
  focusInput: () => void;
}


// Type T is the type of the item that is displayed
// and searched for by the picker. For example, if the picker is
// displaying persona's then type T could either be of Persona or IPersona props
export interface IBasePickerProps<T> extends React.Props<any> {
  /**
   * Optional callback to access the IBasePicker interface. Use this instead of ref for accessing
   * the public methods and properties of the component.
   */
  componentRef?: IRefObject<IBasePicker<T>>;

  /**
   * Function that specifies how the selected item will appear.
   */
  onRenderItem?: (props: IPickerItemProps<T>) => JSX.Element;

  /**
   * Function that specifies how an individual suggestion item will appear.
   */
  onRenderSuggestionsItem?: (props: T, itemProps: any) => JSX.Element;

  /**
   * A callback for what should happen when a person types text into the input.
   * Returns the already selected items so the resolver can filter them out.
   * If used in conjunction with resolveDelay this will ony kick off after the delay throttle.
   */
  onResolveSuggestions: (filter: string, selectedItems?: T[]) => T[] | PromiseLike<T[]>;

  /**
   * The delay time in ms before resolving suggestions, which is kicked off when input has been changed.
   * e.g. If a second input change happens within the resolveDelay time, the timer will start over.
   * Only until after the timer completes will onResolveSuggestions be called.
   */
  resolveDelay?: number;

  /**
   * A callback for what should happen when a user clicks the input.
   */
  onEmptyInputFocus?: (selectedItems?: T[]) => T[] | PromiseLike<T[]>;

  /**
   * Initial items that have already been selected and should appear in the people picker.
   */
  defaultSelectedItems?: T[];

  /**
   * A callback for when the selected list of items changes.
   */
  onChange?: (items?: T[]) => void;

  /**
   * A callback for when the user put focus on the picker
   */
  onFocus?: React.FocusEventHandler<HTMLInputElement | BaseAutoFill>;

  /**
   * A callback for when the user moves the focus away from the picker
   */
  onBlur?: React.FocusEventHandler<HTMLInputElement | BaseAutoFill>;

  /**
   * A callback to get text from an item. Used to autofill text in the pickers.
   */
  getTextFromItem?: (item: T, currentValue?: string) => string;

  /**
   * A callback that gets the rest of the results when a user clicks get more results.
   */
  onGetMoreResults?: (filter: string, selectedItems?: T[]) => T[] | PromiseLike<T[]>;

  /**
   * ClassName for the picker.
   */
  className?: string;

  /**
   * The properties that will get passed to the Suggestions component.
   */
  pickerSuggestionsProps?: IBasePickerSuggestionsProps;

  /**
   * The properties that will get passed to the Callout component.
   */
  pickerCalloutProps?: ICalloutProps;

  /**
   * AutoFill input native props
   * @defaultvalue undefined
   */
  inputProps?: IInputProps;

  /**
   * A callback for when a persona is removed from the suggestion list
   */
  onRemoveSuggestion?: (item: IPersonaProps) => void;

  /**
   * A function used to validate if raw text entered into the well can be added into the selected items list
   */
  onValidateInput?: (input: string) => ValidationState;

  /**
   * The text to display while searching for more results in a limited suggestions list
   */
  searchingText?: ((props: { input: string }) => string) | string;

  /**
   * Flag for disabling the picker.
   * @defaultvalue false
   */
  disabled?: boolean;

  /**
   * Restrict the amount of selectable items.
   * @defaultvalue undefined
   */
  itemLimit?: number;

  /**
   * Function that specifies how arbitrary text entered into the well is handled.
   */
  createGenericItem?: (input: string, ValidationState: ValidationState) => ISuggestionModel<T> | T;

  /**
   * Aria label for the "X" button in the selected item component.
   * @defaultvalue ''
   */
  removeButtonAriaLabel?: string;

  /**
   * A callback to process a selection after the user selects something from the picker. If the callback returns null,
   * the item will not be added to the picker.
   */
  onItemSelected?: (selectedItem?: T) => T | PromiseLike<T> | null;

  /**
   * The items that the base picker should currently display as selected.
   * If this is provided then the picker will act as a controlled component.
   */
  selectedItems?: T[];

  /**
   * A callback used to modify the input string.
   */
  onInputChange?: (input: string) => string;

  /**
   * A callback to override the default behavior of adding the selected suggestion on dismiss.
   */
  onDismiss?: (ev?: any, selectedItem?: T) => void;

  /**
   * Adds an additional alert for the currently selected suggestion. This prop should be set to true for IE11 and below, as it
   * enables proper screen reader behavior for each suggestion (since aria-activedescendant does not work with IE11).
   * It should not be set for modern browsers (Edge, Chrome).
   * @defaultvalue false
   */
  enableSelectedSuggestionAlert?: boolean;

  /**
   * Call to provide customized styling that will layer on top of the variant rules.
   */
  styles?: IStyleFunctionOrObject<IBasePickerStyleProps, IBasePickerStyles>;

  /**
   * Theme provided by styled() function.
   */
  theme?: ITheme;
}


export interface IBasePickerSuggestionsProps {
  /**
   * Function that specifies what to render when no results are found.
   */
  onRenderNoResultFound?: IRenderFunction<void>;

  /**
   * The text that should appear at the top of the suggestion box.
   */
  suggestionsHeaderText?: string;

  /**
   * The text that should appear at the top of the most recently used box.
   */
  mostRecentlyUsedHeaderText?: string;

  /**
   * The text that should appear when no results are returned.
   */
  noResultsFoundText?: string;

  /**
   * Suggestions root className.
   */
  className?: string;

  /**
   * Suggestions List className.
   */
  suggestionsClassName?: string;

  /**
   * ClassName for suggestion items.
   */
  suggestionsItemClassName?: string;

  /**
   * The text that should appear on the button to search for more.
   */
  searchForMoreText?: string;

  /**
   * The text that appears indicating to the use to force resolve the input
   */
  forceResolveText?: string;

  /**
   * The text to display while the results are loading.
   */
  loadingText?: string;

  /**
   * The text to display while searching for more results in a limited suggestions list.
   */
  searchingText?: string;

  /**
   * A renderer that adds an element at the end of the suggestions list if it has more items than resultsMaximumNumber.
   */
  resultsFooterFull?: () => JSX.Element;

  /**
   * A renderer that adds an element at the end of the suggestions list when there are fewer than resultsMaximumNumber.
   */
  resultsFooter?: () => JSX.Element;

  /**
   * Maximum number of suggestions to show in the full suggestion list.
   */
  resultsMaximumNumber?: number;

  /**
   * Indicates whether to show a button with each suggestion to remove that suggestion.
   */
  showRemoveButtons?: boolean;

  /**
   * Screen reader message to read when there are suggestions available.
   */
  suggestionsAvailableAlertText?: string;

  /**
   * An ARIA label for the container that is the parent of the suggestions.
   */
  suggestionsContainerAriaLabel?: string;
}


/** Pickers' input props interface */
export interface IInputProps extends React.InputHTMLAttributes<HTMLInputElement> {
  /**
   * Screen reader label to apply to an input element.
   */
  'aria-label'?: string;

  /**
   * The default value to be visible when the autofill first created.
   * This is different than placeholder text because the placeholder text will disappear and re-appear. This
   * text persists until deleted or changed.
   */
  defaultVisibleValue?: string;
}


/** Represents the stylable areas of the control. */
export interface IBasePickerStyles {
  /** Root element of any picker extending from BasePicker (wraps all the elements). */
  root: IStyle;

  /** Refers to the elements already selected(picked) wrapped by `itemsWrapper` along with the input to type new selection. */
  text: IStyle;

  /** Refers to the items already selected(picked). */
  itemsWrapper: IStyle;

  /** Refers to the input were to type new selections(picks). */
  input: IStyle;

  /** Refers to helper element used for accessibility tools (hidden from view on screen). */
  screenReaderText: IStyle;
}


/** Validation state of the user's input. */
export enum ValidationState {
  /** User input is valid. */
  valid,

  /** User input could be valid or invalid, its state is not known yet. */
  warning,

  /** User input is invalid. */
  invalid
}


/** The props needed to construct styles. */
export type IBasePickerStyleProps = Pick<IBasePickerProps<any>, 'theme' | 'className'> & {
  /** Whether text style area is focused */
  isFocused?: boolean;

  /** Optional pickerInput className */
  inputClassName?: string;
};


(creep) => creep.memory.role === e


export interface SpawnConfig {
    currentPopulation:{ [role: string]: number};
    targetPopulation:{ [role: string]: number};
    presets: {[role: string]: BodyPartConstant[]};
}


public readPresetsFromRoom(room: Room){

        // MINER

        let energyCap: number = room.energyCapacityAvailable;
        let moveParts: number = 1;
        let carryParts: number = 1;
        let workParts: number = 2;

        if (energyCap >= 950) {moveParts=6;carryParts=1;workParts=6;}
        if (energyCap < 950) {moveParts=1;carryParts=1;workParts=6;}
        if (energyCap < 800) {moveParts=1;carryParts=1;workParts=5;}
        if (energyCap < 700) {moveParts=1;carryParts=1;workParts=4;}
        if (energyCap < 500) {moveParts=2;carryParts=1;workParts=1;}

        if (this.currentPopulation.miner === 0 && this.currentPopulation.maintance === 0 && room.energyAvailable < energyCap) {
            moveParts=1;
            carryParts=1;
            workParts=2;
        }

        room.memory.SpawnConfigAutoSpawn.presets.miner = this.presets.miner = Array(workParts).fill(WORK).concat(Array(carryParts).fill(CARRY)).concat(Array(moveParts).fill(MOVE));

        // HAULER

        let carryCap: number = 10;
        if (carryCap < 10) {
            carryCap += 2;
        }

        moveParts= Math.min(Math.max(2,energyCap/150),Math.ceil(carryCap/2));
        carryParts = moveParts*2;

        if (this.currentPopulation.hauler === 0 && room.memory.activeCreepRoles.maintance === 0 && room.energyAvailable < energyCap) {
            moveParts=2;
            carryParts=4;
            this.presets.hauler = Array(carryParts).fill(CARRY).concat(Array(moveParts).fill(MOVE));
        }else{
            let partArray1: BodyPartConstant[] = [];
            for (let i = 0; i < moveParts; i++){
                partArray1 = partArray1.concat([CARRY,CARRY,MOVE]);
            }
            room.memory.SpawnConfigAutoSpawn.presets.hauler =this.presets.hauler = partArray1;
        }

        // MAINTENANCE

            energyCap = Math.min(room.energyCapacityAvailable,1200);
            if (room.memory.activeCreepRoles.hauler === 0 && room.memory.activeCreepRoles.maintance === 0 && room.energyAvailable < energyCap) {
                energyCap = Math.min(room.energyAvailable,1500);
            }

            moveParts = Math.min(Math.max(1,energyCap/200),4);
            carryParts = moveParts;
            workParts = moveParts;
            if (energyCap <= 300) {
                moveParts=2;carryParts=1;workParts=1;
            } else {
                let partArray1: BodyPartConstant[] = [];
                for (let i = 0; i < moveParts; i++){
                    partArray1= partArray1.concat([WORK,CARRY,MOVE]);
                }
                this.presets.maintenance = partArray1;
            }
            room.memory.SpawnConfigAutoSpawn.presets.maintenance = this.presets.maintenance = Array(workParts).fill(WORK).concat(Array(carryParts).fill(CARRY)).concat(Array(moveParts).fill(MOVE));

        // UPGRADER

            energyCap = room.energyCapacityAvailable;
            workParts = Math.max(1,((energyCap-100)/100));
            if (workParts > 15) {
                workParts = 15;
            }
            carryParts = ((energyCap-(100*workParts)-50)/50);
            if (carryParts > 3) {
                carryParts = 3;
            }
            moveParts = ((energyCap-(100*workParts)-50*carryParts)/50);
            if (moveParts > 8) {
                moveParts = 8;
            }
            room.memory.SpawnConfigAutoSpawn.presets.upgrader = this.presets.upgrader = Array(workParts).fill(WORK).concat(Array(carryParts).fill(CARRY)).concat(Array(moveParts).fill(MOVE));

        // DEFENDER

        energyCap = room.energyCapacityAvailable;
        moveParts = 2;
        let attackParts = 2;
        let toughParts = 0;

        if (energyCap >= 1050) {
            moveParts=5;toughParts=0;attackParts=10;
        }
        if (energyCap < 950) {
            moveParts=4;toughParts=0;attackParts=8;
        }
        if (energyCap < 800) {
            moveParts=4;toughParts=0;attackParts=7;
        }
        if (energyCap < 700) {
            moveParts=3;toughParts=0;attackParts=6;
        }
        if (energyCap < 500) {
            moveParts=2;toughParts=0;attackParts=2;
        }

        if (toughParts > 0){
            this.presets.defender = Array(toughParts).fill(TOUGH).concat(Array(moveParts).fill(MOVE)).concat(Array(attackParts).fill(ATTACK));
        }
        else {
            this.presets.defender = Array(moveParts).fill(MOVE).concat(Array(attackParts).fill(ATTACK));
        }

        // HAULER
        energyCap = Math.max(900, room.energyAvailable);
        moveParts= Math.min(16,Math.max(2,(((energyCap)/3)/50)));
        let partArray: BodyPartConstant[] = [];
        for (let i = 0; i < moveParts; i++){
            partArray = partArray.concat([CARRY,CARRY,MOVE]);
        }
        room.memory.SpawnConfigAutoSpawn.presets.hauler = this.presets.hauler = partArray;
    }


public readPresetsFromMemory(room: Room) {
        for(const e of this.roles) {
            this.presets[e]= room.memory.SpawnConfigAutoSpawn.presets[e];
        }
    }


public readCurrentPopulationFromRoom(room: Room): void {
        for(const e of this.roles) {
            this.currentPopulation[e]= room.memory.SpawnConfigAutoSpawn.currentPopulation[e] = room.find(FIND_MY_CREEPS).filter((creep) => creep.memory.role === e).length;
        }
    }


public readTargetPopulationFromRoom(room: Room): void {
        if (room.storage === undefined) {
            room.memory.SpawnConfigAutoSpawn.targetPopulation.miner = this.targetPopulation.miner = 2;
            if(room.energyCapacityAvailable >= 700){
                room.memory.SpawnConfigAutoSpawn.targetPopulation.maintenance = this.targetPopulation.maintenance = 2*Object.keys(room.find(FIND_SOURCES)).length;
            }else if(room.energyCapacityAvailable >= 500){
                room.memory.SpawnConfigAutoSpawn.targetPopulation.maintenance = this.targetPopulation.maintenance = 5*Object.keys(room.find(FIND_SOURCES)).length;
            }else{
                room.memory.SpawnConfigAutoSpawn.targetPopulation.maintenance = this.targetPopulation.maintenance = 4*Object.keys(room.find(FIND_SOURCES)).length;
            }
            room.memory.SpawnConfigAutoSpawn.targetPopulation.upgrader = this.targetPopulation.upgrader = 1;
        } else {
            room.memory.SpawnConfigAutoSpawn.targetPopulation.miner = this.targetPopulation.miner = 2;
            if(room.controller!.level < 8){
                room.memory.SpawnConfigAutoSpawn.targetPopulation.upgrader = this.targetPopulation.upgrader = Math.min(Math.max( (room.storage.store[RESOURCE_ENERGY] / 30000 ) - 4, 0),6);
            }else{
                room.memory.SpawnConfigAutoSpawn.targetPopulation.upgrader = this.targetPopulation.upgrader = 1;
            }
            room.memory.SpawnConfigAutoSpawn.targetPopulation.maintenance = this.targetPopulation.maintenance = Math.ceil(1 + (Object.keys(room.find(FIND_MY_CONSTRUCTION_SITES)).length)/10) ;
        }
        if(room.controller!.level > 5){
            room.memory.SpawnConfigAutoSpawn.targetPopulation.supplier = this.targetPopulation.supplier = 2;
        }
    }


public readTargetPopulationFromMemory(room: Room): void {
        for(const e of this.roles) {
            this.targetPopulation[e]= room.memory.SpawnConfigAutoSpawn.targetPopulation[e];
        }
    }


public initValues(room: Room): void {
        for(const e of this.roles) {
            this.targetPopulation[e]= room.memory.SpawnConfigAutoSpawn.targetPopulation[e] = 0;
            this.currentPopulation[e]= room.memory.SpawnConfigAutoSpawn.currentPopulation[e] = 0;
        }
    }


player => {
      scores.push({
        id: player.id.current,
        level: player.level.current,
        score: player.score.current,
        kills: player.kills.current,
        deaths: player.deaths.current,
        damage: player.damage.current,
        ping: player.ping.current,
      } as ServerPackets.ScoreDetailedScore);
    }


(p1, p2) => p1.score - p2.score


export default class ScoreDetailed extends System {
  constructor({ app }) {
    super({ app });

    this.listeners = {
      [RESPONSE_SCORE_DETAILED]: this.onScoreDetailed,
    };
  }

  /**
   * FFA scores.
   * Sent in response to a client request.
   *
   * @param connectionId player connection id
   */
  onScoreDetailed(connectionId: MainConnectionId): void {
    const scores = [];

    this.storage.playerList.forEach(player => {
      scores.push({
        id: player.id.current,
        level: player.level.current,
        score: player.score.current,
        kills: player.kills.current,
        deaths: player.deaths.current,
        damage: player.damage.current,
        ping: player.ping.current,
      } as ServerPackets.ScoreDetailedScore);
    });

    scores.sort((p1, p2) => p1.score - p2.score);

    this.emit(
      CONNECTIONS_SEND_PACKET,
      {
        c: SERVER_PACKETS.SCORE_DETAILED,
        scores,
      } as ServerPackets.ScoreDetailed,
      connectionId
    );
  }
}


/**
   * FFA scores.
   * Sent in response to a client request.
   *
   * @param connectionId player connection id
   */
onScoreDetailed(connectionId: MainConnectionId): void {
    const scores = [];

    this.storage.playerList.forEach(player => {
      scores.push({
        id: player.id.current,
        level: player.level.current,
        score: player.score.current,
        kills: player.kills.current,
        deaths: player.deaths.current,
        damage: player.damage.current,
        ping: player.ping.current,
      } as ServerPackets.ScoreDetailedScore);
    });

    scores.sort((p1, p2) => p1.score - p2.score);

    this.emit(
      CONNECTIONS_SEND_PACKET,
      {
        c: SERVER_PACKETS.SCORE_DETAILED,
        scores,
      } as ServerPackets.ScoreDetailed,
      connectionId
    );
  }


() => {
  it("should throw an exception if config is missing", () => {
    const init = () => IO.create(fakeConfigPath, fakeFS);
    expect(init).toThrow();
  });

  it("should return assets generation input", () => {
    const io = IO.create(realConfigPath, fakeFS);
    const input = io.getAssetsGenerationInput();
    expect.assertions(3);
    expect(input.rulesets.length).not.toBe(0);
    expect(input.splitByMedia).toBe(false);
    expect(input.media).toEqual({
      md: "only screen and (min-width: 768px)",
      lg: "only screen and (min-width: 1024px)",
    });
  });

  it("should return assets generation props", () => {
    const io = IO.create(realConfigPath, fakeFS);
    const input = io.getAssetsGenerationProps();
    expect(input).toEqual({
      stylesheetProps: { filename: "rececss", extension: "css" },
      rulesetProps: { prefixSep: ":", suffixSep: ":" },
    });
  });

  it("should return css processor input", () => {
    const io = IO.create(realConfigPath, fakeFS);
    const input = io.getCSSProccesorInput();
    expect(input).toEqual({ content: ["./website/pages/**/*.js"] });
  });

  it("should output assets by calling fs.writeFiles with proper filepaths", () => {
    const io = IO.create(realConfigPath, fakeFS);
    io.outputAssets([{ name: "some", contents: "contents" }]);
    expect(fakeFS.writeFiles).toHaveBeenCalledWith([
      { path: path.join("./website/styles", "some"), contents: "contents" },
    ]);
  });
}


() => {
    const init = () => IO.create(fakeConfigPath, fakeFS);
    expect(init).toThrow();
  }


() => IO.create(fakeConfigPath, fakeFS)


() => {
    const io = IO.create(realConfigPath, fakeFS);
    const input = io.getAssetsGenerationInput();
    expect.assertions(3);
    expect(input.rulesets.length).not.toBe(0);
    expect(input.splitByMedia).toBe(false);
    expect(input.media).toEqual({
      md: "only screen and (min-width: 768px)",
      lg: "only screen and (min-width: 1024px)",
    });
  }


() => {
    const io = IO.create(realConfigPath, fakeFS);
    const input = io.getAssetsGenerationProps();
    expect(input).toEqual({
      stylesheetProps: { filename: "rececss", extension: "css" },
      rulesetProps: { prefixSep: ":", suffixSep: ":" },
    });
  }


() => {
    const io = IO.create(realConfigPath, fakeFS);
    const input = io.getCSSProccesorInput();
    expect(input).toEqual({ content: ["./website/pages/**/*.js"] });
  }


() => {
    const io = IO.create(realConfigPath, fakeFS);
    io.outputAssets([{ name: "some", contents: "contents" }]);
    expect(fakeFS.writeFiles).toHaveBeenCalledWith([
      { path: path.join("./website/styles", "some"), contents: "contents" },
    ]);
  }


(state = initialState, action: FluxStandardAction) => {
  switch (action.type) {
    case LOGIN_REQUEST:
      return {
        ...state,
        email: action.payload.credentials.email,
        isFetching: true,
        error: null,
      };

    case LOGIN_SUCCESS:
      return {
        ...state,
        isFetching: false,
        error: null,
        isFirstLogin: action.payload.auth.isFirstLogin,
        name: action.payload.auth.firstName,
        authenticated: true,
      };

    case LOGIN_FAILURE:
      return {
        ...state,
        isFetching: false,
        error: action.payload.error,
      };

    case LOGIN_RESET:
      return {
        ...state,
        email: "",
        error: null,
      };

    case LOGOUT_REQUEST:
    case RESET_AUTHENTICATION:
      return {
        ...initialState,
      };

    case DISMISS_WELCOME_MODAL:
      return {
        ...state,
        hasViewedWelcomeModal: true,
      };

    default:
      return state;
  }
}


export async function getDiaryEmotions({ year, month }: GetDiaryEmotionsProps) {
  const tmpDiaryEmotionList: Array<DiaryEmotion | undefined> = [
    ...Array(31),
  ].map((value, index) =>
    index < new Date().getDate() + 1
      ? {
          id: index,
          emotionColor: EMOTION_COLOR.BLUE,
          createdAt: new Date(year, month, index + 1),
        }
      : undefined,
  );

  return tmpDiaryEmotionList;
}


export async function getDiary(diaryId: number) {
  const tmpDiary: Diary = {
    emotionColor: EMOTION_COLOR.GREEN,
    emotionJournal: '',
    createdAt: new Date(),
  };

  return tmpDiary;
}


export async function postDiary(diary: string) {
  const tmpDiary: Diary = {
    emotionColor: EMOTION_COLOR.GREEN,
    emotionJournal: diary,
    createdAt: new Date(),
  };

  return tmpDiary;
}


(value, index) =>
    index < new Date().getDate() + 1
      ? {
          id: index,
          emotionColor: EMOTION_COLOR.BLUE,
          createdAt: new Date(year, month, index + 1),
        }
      : undefined


interface GetDiaryEmotionsProps {
  year: number;
  month: number;
}


// ----------------------- local functions ------------------------- //
function __getLookups(fieldsInQueryMap: Map<string, SFieldDescribe>): Map<string, string[]> {
      const m: Map<string, string[]> = new Map<string, string[]>();
      fieldsInQueryMap.forEach(field => {
        if (field.referencedObjectType) {
          m.set(field.referencedObjectType, (m.get(field.referencedObjectType) || []).concat(field.name));
        }
      });
      return m;
    }


// ----------------- Helpers ---------------------------- //
function __addScriptObject(script: ISfdmuRunScript, object: ISfdmuRunScriptObject): {
      object: ISfdmuRunScriptObject,
      parsedQuery: Query
    } {
      let newObject = script.addScriptObject({
        objectName: object.objectName,
        operation: OPERATION.Readonly
      });
      return {
        object: newObject,
        parsedQuery: parseQuery(newObject.query)
      };
    }


field => {
        let task = job.tasks.find(task => task.sObjectName == field.sourceObject);
        if (!task) {
          this.runtime.logFormattedInfo(this,
            SFDMU_RUN_ADDON_MESSAGES.RecordsTransform_SourceTaskNotFound,
            field.sourceField,
            field.sourceObject);
        } else {
          if (task.sourceTaskData.records.length
            && !task.sourceTaskData.records[0].hasOwnProperty(field.sourceField)) {
            this.runtime.logFormattedInfo(this,
              SFDMU_RUN_ADDON_MESSAGES.RecordsTransform_SourceFieldNotFound,
              field.sourceField,
              field.sourceObject);
          } else {
            if (task.fieldsInQuery.indexOf(field.sourceField) < 0) {
              this.runtime.logFormattedInfo(this,
                SFDMU_RUN_ADDON_MESSAGES.RecordsTransform_SourceFieldNotFound,
                field.sourceField,
                field.sourceObject);
            } else {
              fieldsMap.set(field.alias, Object.assign(field, {
                sourceTask: task,
                sourceSFieldDescribe: task.fieldsInQueryMap.get(field.sourceField),
                lookupFieldMap: __getLookups(task.fieldsInQueryMap)
              }));
            }

          }
        }
      }


task => task.sObjectName == field.sourceObject


transformation => {
        let task = job.tasks.find(task => task.sObjectName == transformation.targetObject);
        if (!task) {
          this.runtime.logFormattedInfo(this,
            SFDMU_RUN_ADDON_MESSAGES.RecordsTransform_TargetFieldNotFound,
            transformation.targetField,
            transformation.targetObject);
        } else {
          transformsMap.set(transformation.targetField, Object.assign(transformation, {
            targetTask: task,
            targetSFieldDescribe: task.fieldsInQueryMap.get(transformation.targetField),
            lookupFieldMap: __getLookups(task.fieldsInQueryMap)
          }));
        }
      }


task => task.sObjectName == transformation.targetObject


(transformation: ITransformation) => {

        let targetRecords = transformation.targetTask.sourceTaskData.records;

        targetRecords.forEach(targetRecord => {

          let formula = {};

          // Generate formula properties for further eval() on the targetRecord
          fieldsMap.forEach((field: IField) => {

            if (transformation.targetObject == field.sourceObject) {
              // The same object => direct transformation:
              //      source field => target field on the same targetRecord
              formula[field.alias] = targetRecord[field.sourceField];
            } else {
              // Different object => lookup based transformation
              let sourceIdFields = transformation.lookupFieldMap.get(field.sourceObject);
              if (sourceIdFields) {
                // Target => source
                sourceIdFields.forEach(sourceIdField => {
                  let sourceId = targetRecord[sourceIdField];
                  let sourceRecord = field.sourceTask.sourceTaskData.idRecordsMap.get(sourceId);
                  if (sourceRecord) {
                    formula[field.alias] = sourceRecord[field.sourceField];
                  }
                });
              } else {
                // Source => target
                let targetIdFields = field.lookupFieldMap.get(transformation.targetObject);
                if (targetIdFields) {
                  let targetId = targetRecord["Id"];
                  if (targetId) {
                    // Find all the source records which is pointing to the current targetId
                    let sourceRecords = field.sourceTask.sourceTaskData.records;
                    for (let index = 0; index < sourceRecords.length; index++) {
                      const sourceRecord = sourceRecords[index];
                      if (Object.keys(sourceRecord).some(fieldName => {
                        return sourceRecord[fieldName] == targetId;
                      })) {
                        formula[field.alias] = sourceRecord[field.sourceField];
                        break;
                      }
                    }
                  }
                }
              }
            }

            formula[field.alias] = formula[field.alias] || null;
          });

          // Expresisons
          if (transformation.expressions) {
            transformation.expressions.forEach(expression => eval(expression));
          }

          // Evaluate
          targetRecord[transformation.targetField] = eval(transformation.formula);

        });

      }


targetRecord => {

          let formula = {};

          // Generate formula properties for further eval() on the targetRecord
          fieldsMap.forEach((field: IField) => {

            if (transformation.targetObject == field.sourceObject) {
              // The same object => direct transformation:
              //      source field => target field on the same targetRecord
              formula[field.alias] = targetRecord[field.sourceField];
            } else {
              // Different object => lookup based transformation
              let sourceIdFields = transformation.lookupFieldMap.get(field.sourceObject);
              if (sourceIdFields) {
                // Target => source
                sourceIdFields.forEach(sourceIdField => {
                  let sourceId = targetRecord[sourceIdField];
                  let sourceRecord = field.sourceTask.sourceTaskData.idRecordsMap.get(sourceId);
                  if (sourceRecord) {
                    formula[field.alias] = sourceRecord[field.sourceField];
                  }
                });
              } else {
                // Source => target
                let targetIdFields = field.lookupFieldMap.get(transformation.targetObject);
                if (targetIdFields) {
                  let targetId = targetRecord["Id"];
                  if (targetId) {
                    // Find all the source records which is pointing to the current targetId
                    let sourceRecords = field.sourceTask.sourceTaskData.records;
                    for (let index = 0; index < sourceRecords.length; index++) {
                      const sourceRecord = sourceRecords[index];
                      if (Object.keys(sourceRecord).some(fieldName => {
                        return sourceRecord[fieldName] == targetId;
                      })) {
                        formula[field.alias] = sourceRecord[field.sourceField];
                        break;
                      }
                    }
                  }
                }
              }
            }

            formula[field.alias] = formula[field.alias] || null;
          });

          // Expresisons
          if (transformation.expressions) {
            transformation.expressions.forEach(expression => eval(expression));
          }

          // Evaluate
          targetRecord[transformation.targetField] = eval(transformation.formula);

        }


(field: IField) => {

            if (transformation.targetObject == field.sourceObject) {
              // The same object => direct transformation:
              //      source field => target field on the same targetRecord
              formula[field.alias] = targetRecord[field.sourceField];
            } else {
              // Different object => lookup based transformation
              let sourceIdFields = transformation.lookupFieldMap.get(field.sourceObject);
              if (sourceIdFields) {
                // Target => source
                sourceIdFields.forEach(sourceIdField => {
                  let sourceId = targetRecord[sourceIdField];
                  let sourceRecord = field.sourceTask.sourceTaskData.idRecordsMap.get(sourceId);
                  if (sourceRecord) {
                    formula[field.alias] = sourceRecord[field.sourceField];
                  }
                });
              } else {
                // Source => target
                let targetIdFields = field.lookupFieldMap.get(transformation.targetObject);
                if (targetIdFields) {
                  let targetId = targetRecord["Id"];
                  if (targetId) {
                    // Find all the source records which is pointing to the current targetId
                    let sourceRecords = field.sourceTask.sourceTaskData.records;
                    for (let index = 0; index < sourceRecords.length; index++) {
                      const sourceRecord = sourceRecords[index];
                      if (Object.keys(sourceRecord).some(fieldName => {
                        return sourceRecord[fieldName] == targetId;
                      })) {
                        formula[field.alias] = sourceRecord[field.sourceField];
                        break;
                      }
                    }
                  }
                }
              }
            }

            formula[field.alias] = formula[field.alias] || null;
          }


sourceIdField => {
                  let sourceId = targetRecord[sourceIdField];
                  let sourceRecord = field.sourceTask.sourceTaskData.idRecordsMap.get(sourceId);
                  if (sourceRecord) {
                    formula[field.alias] = sourceRecord[field.sourceField];
                  }
                }


fieldName => {
                        return sourceRecord[fieldName] == targetId;
                      }


expression => eval(expression)


(transformation: ITransformation) => {
        let targetRecords = transformation.targetTask.sourceTaskData.records;
        transformation.targetTask.mapRecords(targetRecords);
      }


field => {
        if (field.referencedObjectType) {
          m.set(field.referencedObjectType, (m.get(field.referencedObjectType) || []).concat(field.name));
        }
      }


object => {
      return {
        object,
        parsedQuery: parseQuery(object.query)
      };
    }


field => {
      let object = objects.find(ob => ob.parsedQuery.sObject == field.sourceObject);

      if (!object) {
        object = __addScriptObject(script, {
          objectName: field.sourceObject,
          operation: OPERATION.Readonly
        });
        objects.push(object);
      }

      Common.addOrRemoveQueryFields(object.parsedQuery, [field.sourceField]);

      if (field.includeLookupFields) {
        field.includeLookupFields.forEach(field => {
          if (field) {
            let parts = field.split('.');
            if (parts.length == 1) {
              Common.addOrRemoveQueryFields(object.parsedQuery, [field]);
            } else {
              let obj = objects.find(ob => ob.parsedQuery.sObject == parts[0]);
              if (!obj) {
                obj = __addScriptObject(script, {
                  objectName: parts[0],
                  operation: OPERATION.Readonly
                });
                objects.push(obj);
              }
              Common.addOrRemoveQueryFields(obj.parsedQuery, [parts[1]]);
            }
          }
        });
      }

    }


ob => ob.parsedQuery.sObject == field.sourceObject


field => {
          if (field) {
            let parts = field.split('.');
            if (parts.length == 1) {
              Common.addOrRemoveQueryFields(object.parsedQuery, [field]);
            } else {
              let obj = objects.find(ob => ob.parsedQuery.sObject == parts[0]);
              if (!obj) {
                obj = __addScriptObject(script, {
                  objectName: parts[0],
                  operation: OPERATION.Readonly
                });
                objects.push(obj);
              }
              Common.addOrRemoveQueryFields(obj.parsedQuery, [parts[1]]);
            }
          }
        }


ob => ob.parsedQuery.sObject == parts[0]


transformation => {
      let object = objects.find(ob => ob.parsedQuery.sObject == transformation.targetObject);

      if (!object) {
        object = __addScriptObject(script, {
          objectName: transformation.targetObject,
          operation: OPERATION.Readonly
        });
        objects.push(object);
      }

      Common.addOrRemoveQueryFields(object.parsedQuery, [transformation.targetField]);

      if (transformation.includeLookupFields) {
        transformation.includeLookupFields.forEach(field => {
          if (field) {
            let parts = field.split('.');
            if (parts.length == 1) {
              Common.addOrRemoveQueryFields(object.parsedQuery, [field]);
            } else {
              let obj = objects.find(ob => ob.parsedQuery.sObject == parts[0]);
              if (!obj) {
                obj = __addScriptObject(script, {
                  objectName: parts[0],
                  operation: OPERATION.Readonly
                });
                objects.push(obj);
              }
              Common.addOrRemoveQueryFields(obj.parsedQuery, [parts[1]]);
            }
          }
        });
      }

      let scriptObject = script.objects.find(object => object.name == transformation.targetObject);
      if (scriptObject) {
        if (scriptObject.extraFieldsToUpdate.indexOf(transformation.targetField) < 0) {
          scriptObject.extraFieldsToUpdate = scriptObject.extraFieldsToUpdate.concat(transformation.targetField);
        }
      }
    }


ob => ob.parsedQuery.sObject == transformation.targetObject


object => object.name == transformation.targetObject


object => {
      object.object.query = composeQuery(object.parsedQuery);
    }


interface ITransformation {

  // Original -----
  targetObject: string
  targetField: string,
  formula: string,
  includeLookupFields: Array<string>,
  expressions: Array<string>,

  // Runtime ------
  targetTask: SfdmuRunAddonTask,
  targetSFieldDescribe: SFieldDescribe,
  lookupFieldMap: Map<string, string[]> // referenced object => child id fields
}


interface IField {

  // Original -----
  alias: string,
  sourceObject: string,
  sourceField: string,
  includeLookupFields: Array<string>,

  // Runtime ------
  sourceTask: SfdmuRunAddonTask,
  sourceSFieldDescribe: SFieldDescribe,
  lookupFieldMap: Map<string, string[]> // referenced object => child id fields
}


interface IOnExecuteArguments {
  fields: Array<IField>;
  transformations: Array<ITransformation>;
}


async onExecute(context: IAddonContext, args: IOnExecuteArguments): Promise<AddonResult> {

    this.runtime.logAddonExecutionStarted(this);

    // Verify current event
    if (!this.runtime.validateSupportedEvents(this, CONST.SUPPORTED_EVENTS)) {
      this.runtime.logFormattedWarning(this,
        SFDMU_RUN_ADDON_MESSAGES.General_EventNotSupported,
        context.eventName,
        context.moduleDisplayName,
        CONST.SUPPORTED_EVENTS.join());
      return null;
    }

    const job = this.runtime.pluginJob;
    const fieldsMap: Map<string, IField> = new Map<string, IField>();
    const transformsMap: Map<string, ITransformation> = new Map<string, ITransformation>();

    this.runtime.logFormattedInfo(this, SFDMU_RUN_ADDON_MESSAGES.General_CheckingArgs);

    // Verify module args
    if (!args) {
      this.runtime.logFormattedWarning(this,
        SFDMU_RUN_ADDON_MESSAGES.General_ArgumentsCannotBeParsed);
      return null;
    }

    this.runtime.logFormattedInfo(this, SFDMU_RUN_ADDON_MESSAGES.RecordsTransform_CreatingMappingScheme);

    // Create fields map
    try {
      args.fields.forEach(field => {
        let task = job.tasks.find(task => task.sObjectName == field.sourceObject);
        if (!task) {
          this.runtime.logFormattedInfo(this,
            SFDMU_RUN_ADDON_MESSAGES.RecordsTransform_SourceTaskNotFound,
            field.sourceField,
            field.sourceObject);
        } else {
          if (task.sourceTaskData.records.length
            && !task.sourceTaskData.records[0].hasOwnProperty(field.sourceField)) {
            this.runtime.logFormattedInfo(this,
              SFDMU_RUN_ADDON_MESSAGES.RecordsTransform_SourceFieldNotFound,
              field.sourceField,
              field.sourceObject);
          } else {
            if (task.fieldsInQuery.indexOf(field.sourceField) < 0) {
              this.runtime.logFormattedInfo(this,
                SFDMU_RUN_ADDON_MESSAGES.RecordsTransform_SourceFieldNotFound,
                field.sourceField,
                field.sourceObject);
            } else {
              fieldsMap.set(field.alias, Object.assign(field, {
                sourceTask: task,
                sourceSFieldDescribe: task.fieldsInQueryMap.get(field.sourceField),
                lookupFieldMap: __getLookups(task.fieldsInQueryMap)
              }));
            }

          }
        }
      });

      // Create transformations map
      args.transformations.forEach(transformation => {
        let task = job.tasks.find(task => task.sObjectName == transformation.targetObject);
        if (!task) {
          this.runtime.logFormattedInfo(this,
            SFDMU_RUN_ADDON_MESSAGES.RecordsTransform_TargetFieldNotFound,
            transformation.targetField,
            transformation.targetObject);
        } else {
          transformsMap.set(transformation.targetField, Object.assign(transformation, {
            targetTask: task,
            targetSFieldDescribe: task.fieldsInQueryMap.get(transformation.targetField),
            lookupFieldMap: __getLookups(task.fieldsInQueryMap)
          }));
        }
      });

      this.runtime.logFormattedInfo(this, SFDMU_RUN_ADDON_MESSAGES.RecordsTransform_Tranforming);

      // Execute
      transformsMap.forEach((transformation: ITransformation) => {

        let targetRecords = transformation.targetTask.sourceTaskData.records;

        targetRecords.forEach(targetRecord => {

          let formula = {};

          // Generate formula properties for further eval() on the targetRecord
          fieldsMap.forEach((field: IField) => {

            if (transformation.targetObject == field.sourceObject) {
              // The same object => direct transformation:
              //      source field => target field on the same targetRecord
              formula[field.alias] = targetRecord[field.sourceField];
            } else {
              // Different object => lookup based transformation
              let sourceIdFields = transformation.lookupFieldMap.get(field.sourceObject);
              if (sourceIdFields) {
                // Target => source
                sourceIdFields.forEach(sourceIdField => {
                  let sourceId = targetRecord[sourceIdField];
                  let sourceRecord = field.sourceTask.sourceTaskData.idRecordsMap.get(sourceId);
                  if (sourceRecord) {
                    formula[field.alias] = sourceRecord[field.sourceField];
                  }
                });
              } else {
                // Source => target
                let targetIdFields = field.lookupFieldMap.get(transformation.targetObject);
                if (targetIdFields) {
                  let targetId = targetRecord["Id"];
                  if (targetId) {
                    // Find all the source records which is pointing to the current targetId
                    let sourceRecords = field.sourceTask.sourceTaskData.records;
                    for (let index = 0; index < sourceRecords.length; index++) {
                      const sourceRecord = sourceRecords[index];
                      if (Object.keys(sourceRecord).some(fieldName => {
                        return sourceRecord[fieldName] == targetId;
                      })) {
                        formula[field.alias] = sourceRecord[field.sourceField];
                        break;
                      }
                    }
                  }
                }
              }
            }

            formula[field.alias] = formula[field.alias] || null;
          });

          // Expresisons
          if (transformation.expressions) {
            transformation.expressions.forEach(expression => eval(expression));
          }

          // Evaluate
          targetRecord[transformation.targetField] = eval(transformation.formula);

        });

      });

      this.runtime.logFormattedInfo(this, SFDMU_RUN_ADDON_MESSAGES.RecordsTransform_AppliedValueMapping);

      // Map values
      transformsMap.forEach((transformation: ITransformation) => {
        let targetRecords = transformation.targetTask.sourceTaskData.records;
        transformation.targetTask.mapRecords(targetRecords);
      });

    } catch (e) {
      this.runtime.logFormattedWarning(this,
        SFDMU_RUN_ADDON_MESSAGES.General_AddOnRuntimeError,
        context.moduleDisplayName);
      return null;
    }

    this.runtime.logAddonExecutionFinished(this);

    return null;

    // ----------------------- local functions ------------------------- //
    function __getLookups(fieldsInQueryMap: Map<string, SFieldDescribe>): Map<string, string[]> {
      const m: Map<string, string[]> = new Map<string, string[]>();
      fieldsInQueryMap.forEach(field => {
        if (field.referencedObjectType) {
          m.set(field.referencedObjectType, (m.get(field.referencedObjectType) || []).concat(field.name));
        }
      });
      return m;
    }
  }


async onInit(context: IAddonContext, args: IOnExecuteArguments): Promise<AddonResult> {

    let script = this.runtime.getScript();

    // Add all extra fields used in the all transformation to the original query string
    let objects = script.objects.map(object => {
      return {
        object,
        parsedQuery: parseQuery(object.query)
      };
    });

    args.fields.forEach(field => {
      let object = objects.find(ob => ob.parsedQuery.sObject == field.sourceObject);

      if (!object) {
        object = __addScriptObject(script, {
          objectName: field.sourceObject,
          operation: OPERATION.Readonly
        });
        objects.push(object);
      }

      Common.addOrRemoveQueryFields(object.parsedQuery, [field.sourceField]);

      if (field.includeLookupFields) {
        field.includeLookupFields.forEach(field => {
          if (field) {
            let parts = field.split('.');
            if (parts.length == 1) {
              Common.addOrRemoveQueryFields(object.parsedQuery, [field]);
            } else {
              let obj = objects.find(ob => ob.parsedQuery.sObject == parts[0]);
              if (!obj) {
                obj = __addScriptObject(script, {
                  objectName: parts[0],
                  operation: OPERATION.Readonly
                });
                objects.push(obj);
              }
              Common.addOrRemoveQueryFields(obj.parsedQuery, [parts[1]]);
            }
          }
        });
      }

    });

    args.transformations.forEach(transformation => {
      let object = objects.find(ob => ob.parsedQuery.sObject == transformation.targetObject);

      if (!object) {
        object = __addScriptObject(script, {
          objectName: transformation.targetObject,
          operation: OPERATION.Readonly
        });
        objects.push(object);
      }

      Common.addOrRemoveQueryFields(object.parsedQuery, [transformation.targetField]);

      if (transformation.includeLookupFields) {
        transformation.includeLookupFields.forEach(field => {
          if (field) {
            let parts = field.split('.');
            if (parts.length == 1) {
              Common.addOrRemoveQueryFields(object.parsedQuery, [field]);
            } else {
              let obj = objects.find(ob => ob.parsedQuery.sObject == parts[0]);
              if (!obj) {
                obj = __addScriptObject(script, {
                  objectName: parts[0],
                  operation: OPERATION.Readonly
                });
                objects.push(obj);
              }
              Common.addOrRemoveQueryFields(obj.parsedQuery, [parts[1]]);
            }
          }
        });
      }

      let scriptObject = script.objects.find(object => object.name == transformation.targetObject);
      if (scriptObject) {
        if (scriptObject.extraFieldsToUpdate.indexOf(transformation.targetField) < 0) {
          scriptObject.extraFieldsToUpdate = scriptObject.extraFieldsToUpdate.concat(transformation.targetField);
        }
      }
    });

    objects.forEach(object => {
      object.object.query = composeQuery(object.parsedQuery);
    });

    return null;

    // ----------------- Helpers ---------------------------- //
    function __addScriptObject(script: ISfdmuRunScript, object: ISfdmuRunScriptObject): {
      object: ISfdmuRunScriptObject,
      parsedQuery: Query
    } {
      let newObject = script.addScriptObject({
        objectName: object.objectName,
        operation: OPERATION.Readonly
      });
      return {
        object: newObject,
        parsedQuery: parseQuery(newObject.query)
      };
    }
  }


/**
 * @class IBaseModelOptions
 * @interface
 */
export interface IBaseModelOptions {
    /**
     * @property expand
     * @type {boolean}
     */
    expand?: boolean;
}


export function BulletItem(props: BulletItemProps) {
  return (
    <View style={BULLET_ITEM}>
      <Icon icon="bullet" containerStyle={BULLET_CONTAINER} style={BULLET} />
      <Text style={BULLET_TEXT} text={props.text} />
    </View>
  )
}


export interface BulletItemProps {
  text: string
}


export class AccountPom {
  constructor(private page: Page) {}

  async navigateTo() {
    await this.page.goto(`${process.env.WEBSITE_PATH}/dashboard/account`);
  }

  async verifyEmailShown(value: string) {
    await expect(this.page.locator("[test='email']")).toContainText(value);
  }

  async verifyCreationDate(value: string) {
    await expect(this.page.locator("[test='member-since']")).toContainText(
      value
    );
  }

  async resetPassword() {
    await this.page.click("[test='reset-password']");
  }

  async verifySuccessMsgShown() {
    await expect(
      this.page.locator("[test='reset-pw-success-msg']")
    ).toBeVisible();
  }
}


async navigateTo() {
    await this.page.goto(`${process.env.WEBSITE_PATH}/dashboard/account`);
  }


async verifyEmailShown(value: string) {
    await expect(this.page.locator("[test='email']")).toContainText(value);
  }


async verifyCreationDate(value: string) {
    await expect(this.page.locator("[test='member-since']")).toContainText(
      value
    );
  }


async resetPassword() {
    await this.page.click("[test='reset-password']");
  }


async verifySuccessMsgShown() {
    await expect(
      this.page.locator("[test='reset-pw-success-msg']")
    ).toBeVisible();
  }


(_to, _from, next) => {
			if (this.changed) {
				if (
					!window.confirm(
						this.$gettext(`Are you sure you want to discard your unsaved changes?`)
					)
				) {
					return next(false);
				}
			}
			next();
		}


i => i === error


() => {
			this.state.isShowingSuccess = false;
			this.successClearTimeout = undefined;
		}


@Component({
	components: {
		...CommonFormComponents,
	},
})
export class BaseForm<T> extends Vue {
	@Prop(Object) model?: Readonly<T>;

	formModel: Readonly<T> = {} as T;
	modelClass?: { new (data?: T): T } = undefined;
	resetOnSubmit = false;
	warnOnDiscard = true;
	saveMethod?: keyof T;
	method: 'add' | 'edit' = 'add';
	changed = false;
	attemptedSubmit = false;
	hasFormErrors = false;

	// These get overriden as getters in the child classes.
	readonly loadData: any | null;
	isLoaded: boolean | null = null;
	isLoadedBootstrapped: boolean | null = null;
	reloadOnSubmit = false;

	private changeDeregister?: Function;

	state = {
		isProcessing: false,
		isShowingSuccess: false,
	};

	successClearTimeout?: NodeJS.Timer;
	serverErrors: PayloadFormErrors = {};
	private customErrors: string[] = [];

	get loadUrl(): null | string {
		return null;
	}

	get valid() {
		return !this.hasFormErrors && this.customErrors.length === 0;
	}

	created() {
		this.privateInit();
	}

	mounted() {
		if (!this.warnOnDiscard) {
			return;
		}

		this.changeDeregister = this.$router.beforeEach((_to, _from, next) => {
			if (this.changed) {
				if (
					!window.confirm(
						this.$gettext(`Are you sure you want to discard your unsaved changes?`)
					)
				) {
					return next(false);
				}
			}
			next();
		});
	}

	destroyed() {
		if (this.changeDeregister) {
			this.changeDeregister();
			this.changeDeregister = undefined;
		}
	}

	private privateInit() {
		// Is a base model defined? If so, then we're editing.
		if (this.model) {
			this.method = 'edit';

			// If a model class was assigned to this form, then create a copy of
			// it on the instance. Otherwise just copy the object.
			if (this.modelClass) {
				this.formModel = new this.modelClass(this.model);
			} else {
				this.formModel = Object.assign({}, this.model);
			}
		} else {
			// If we have a model class, then create a new one.
			if (this.modelClass) {
				this.formModel = new this.modelClass();
			} else {
				// Otherwise, just use an empty object as the form's model.
				this.formModel = {} as T;
			}
		}

		// This is the main way for forms to initialize.
		if ((this as any).onInit) {
			(this as any).onInit();
		}

		this._load();
	}

	private async _load() {
		if (this.isLoaded && !this.reloadOnSubmit) {
			return;
		}

		this.isLoaded = null;
		if (!this.loadUrl) {
			return;
		}

		this.isLoaded = false;

		const payload = await Api.sendRequest(this.loadUrl, this.loadData || undefined, {
			detach: true,
		});

		this.isLoaded = true;
		this.isLoadedBootstrapped = true;
		if ((this as any).onLoad) {
			(this as any).onLoad(payload);
		}
	}

	/**
	 * When setting form model field values we need to make sure Vue knows that
	 * the field has changed. This ensures that we always let Vue know any time
	 * we change a field.
	 */
	setField<K extends keyof T>(key: K, value: T[K]) {
		Vue.set(this.formModel as any, key as any, value);
	}

	setCustomError(error: string) {
		this.customErrors.push(error);
		this.customErrors = arrayUnique(this.customErrors);
	}

	clearCustomError(error: string) {
		arrayRemove(this.customErrors, i => i === error);
	}

	hasCustomError(error: string) {
		return this.customErrors.indexOf(error) !== -1;
	}

	async _onSubmit() {
		if (this.state.isProcessing) {
			return false;
		}

		this.state.isProcessing = true;

		let response: any;

		try {
			if ((this as any).onBeforeSubmit) {
				(this as any).onBeforeSubmit();
			}

			if ((this as any).onSubmit) {
				const _response = await (this as any).onSubmit();
				if (_response.success === false) {
					throw _response;
				}

				response = _response;
			} else if (this.modelClass) {
				response = await (this.formModel as any)[this.saveMethod || '$save']();

				// Copy it back to the base model.
				if (this.model) {
					Object.assign(this.model, this.formModel);
				}
			}

			if ((this as any).onSubmitSuccess) {
				(this as any).onSubmitSuccess(response);
			}

			// Reset our state.
			this.state.isProcessing = false;
			this.changed = false;
			this.attemptedSubmit = false;
			this.serverErrors = {};

			// Show successful form submission.
			this._showSuccess();

			// Send the new model back into the submit handler.
			this.$emit('submit', this.formModel, response);

			// If we should reset on successful submit, let's do that now.
			if (this.resetOnSubmit) {
				this.privateInit();

				// Reset again in case init triggered changes.
				this.changed = false;
			}

			return true;
		} catch (_response) {
			console.error('Form error', _response);

			// Store the server validation errors.
			if (_response && _response.errors) {
				this.serverErrors = _response.errors;
			}

			if ((this as any).onSubmitError) {
				(this as any).onSubmitError(_response);
			}

			// Reset our processing state.
			this.state.isProcessing = false;
			return false;
		}
	}

	private _showSuccess() {
		// Reset the timeout if it's already showing.
		if (this.successClearTimeout) {
			clearTimeout(this.successClearTimeout);
		}

		this.state.isShowingSuccess = true;

		this.successClearTimeout = setTimeout(() => {
			this.state.isShowingSuccess = false;
			this.successClearTimeout = undefined;
		}, 2000);
	}
}


export interface FormOnInit {
	onInit(): void;
}


export interface FormOnBeforeSubmit {
	onBeforeSubmit(): void;
}


export interface FormOnSubmit {
	onSubmit(): Promise<any>;
}


export interface FormOnLoad {
	readonly loadUrl: string;
	readonly loadData?: any;
	onLoad(response: any): void;
}


export interface FormOnSubmitSuccess {
	onSubmitSuccess(response: any): void;
}


export interface FormOnSubmitError {
	onSubmitError(response: any): void;
}


created() {
		this.privateInit();
	}


mounted() {
		if (!this.warnOnDiscard) {
			return;
		}

		this.changeDeregister = this.$router.beforeEach((_to, _from, next) => {
			if (this.changed) {
				if (
					!window.confirm(
						this.$gettext(`Are you sure you want to discard your unsaved changes?`)
					)
				) {
					return next(false);
				}
			}
			next();
		});
	}


destroyed() {
		if (this.changeDeregister) {
			this.changeDeregister();
			this.changeDeregister = undefined;
		}
	}


private privateInit() {
		// Is a base model defined? If so, then we're editing.
		if (this.model) {
			this.method = 'edit';

			// If a model class was assigned to this form, then create a copy of
			// it on the instance. Otherwise just copy the object.
			if (this.modelClass) {
				this.formModel = new this.modelClass(this.model);
			} else {
				this.formModel = Object.assign({}, this.model);
			}
		} else {
			// If we have a model class, then create a new one.
			if (this.modelClass) {
				this.formModel = new this.modelClass();
			} else {
				// Otherwise, just use an empty object as the form's model.
				this.formModel = {} as T;
			}
		}

		// This is the main way for forms to initialize.
		if ((this as any).onInit) {
			(this as any).onInit();
		}

		this._load();
	}


private async _load() {
		if (this.isLoaded && !this.reloadOnSubmit) {
			return;
		}

		this.isLoaded = null;
		if (!this.loadUrl) {
			return;
		}

		this.isLoaded = false;

		const payload = await Api.sendRequest(this.loadUrl, this.loadData || undefined, {
			detach: true,
		});

		this.isLoaded = true;
		this.isLoadedBootstrapped = true;
		if ((this as any).onLoad) {
			(this as any).onLoad(payload);
		}
	}


/**
	 * When setting form model field values we need to make sure Vue knows that
	 * the field has changed. This ensures that we always let Vue know any time
	 * we change a field.
	 */
setField<K extends keyof T>(key: K, value: T[K]) {
		Vue.set(this.formModel as any, key as any, value);
	}


setCustomError(error: string) {
		this.customErrors.push(error);
		this.customErrors = arrayUnique(this.customErrors);
	}


clearCustomError(error: string) {
		arrayRemove(this.customErrors, i => i === error);
	}


hasCustomError(error: string) {
		return this.customErrors.indexOf(error) !== -1;
	}


async _onSubmit() {
		if (this.state.isProcessing) {
			return false;
		}

		this.state.isProcessing = true;

		let response: any;

		try {
			if ((this as any).onBeforeSubmit) {
				(this as any).onBeforeSubmit();
			}

			if ((this as any).onSubmit) {
				const _response = await (this as any).onSubmit();
				if (_response.success === false) {
					throw _response;
				}

				response = _response;
			} else if (this.modelClass) {
				response = await (this.formModel as any)[this.saveMethod || '$save']();

				// Copy it back to the base model.
				if (this.model) {
					Object.assign(this.model, this.formModel);
				}
			}

			if ((this as any).onSubmitSuccess) {
				(this as any).onSubmitSuccess(response);
			}

			// Reset our state.
			this.state.isProcessing = false;
			this.changed = false;
			this.attemptedSubmit = false;
			this.serverErrors = {};

			// Show successful form submission.
			this._showSuccess();

			// Send the new model back into the submit handler.
			this.$emit('submit', this.formModel, response);

			// If we should reset on successful submit, let's do that now.
			if (this.resetOnSubmit) {
				this.privateInit();

				// Reset again in case init triggered changes.
				this.changed = false;
			}

			return true;
		} catch (_response) {
			console.error('Form error', _response);

			// Store the server validation errors.
			if (_response && _response.errors) {
				this.serverErrors = _response.errors;
			}

			if ((this as any).onSubmitError) {
				(this as any).onSubmitError(_response);
			}

			// Reset our processing state.
			this.state.isProcessing = false;
			return false;
		}
	}


private _showSuccess() {
		// Reset the timeout if it's already showing.
		if (this.successClearTimeout) {
			clearTimeout(this.successClearTimeout);
		}

		this.state.isShowingSuccess = true;

		this.successClearTimeout = setTimeout(() => {
			this.state.isShowingSuccess = false;
			this.successClearTimeout = undefined;
		}, 2000);
	}


() => {
    this.isLoading = true;
    storage
      .ref("menus")
      .child(this.filename)
      .getDownloadURL()
      .then(
        action((url: string) => {
          this.imageURL = url;
          this.isLoading = false;
        })
      )
      .catch(
        action((error: Error) => {
          this.isExist = false;
          this.isLoading = false;
        })
      );
  }


(url: string) => {
          this.imageURL = url;
          this.isLoading = false;
        }


(error: Error) => {
          this.isExist = false;
          this.isLoading = false;
        }


class MenuStore implements IMenuStore {
  public rootStore;

  @observable
  public isLoading;

  @observable
  public isExist;

  @observable
  public imageURL;

  private filename = utils.getWeekNumber().join("-");

  constructor(rootStore) {
    this.rootStore = rootStore;
    this.isLoading = false;
    this.isExist = true;
    this.imageURL = "";
  }

  @action
  public fetchMenu = () => {
    this.isLoading = true;
    storage
      .ref("menus")
      .child(this.filename)
      .getDownloadURL()
      .then(
        action((url: string) => {
          this.imageURL = url;
          this.isLoading = false;
        })
      )
      .catch(
        action((error: Error) => {
          this.isExist = false;
          this.isLoading = false;
        })
      );
  };
}


export interface SwitchProps
  extends StandardProps<SwitchBaseProps, SwitchClassKey, 'checkedIcon' | 'color' | 'icon'> {
  checkedIcon?: React.ReactNode;
  color?: 'primary' | 'secondary' | 'default';
  icon?: React.ReactNode;
}


export type SwitchClassKey =
  | SwitchBaseClassKey
  | 'bar'
  | 'icon'
  | 'iconChecked'
  | 'checkedPrimary'
  | 'checkedSecondary';


/** @soyCompatible */
@customElement('mwc-checkbox')
export class Checkbox extends CheckboxBase {
  static override styles = [styles];
}


interface HTMLElementTagNameMap {
    'mwc-checkbox': Checkbox;
  }


export default class MockBridgePlugin extends BridgePlugin {
  private host?: string;

  public async init(getPlugin: GetPlugin) {
    const configPlugin = getPlugin(ConfigPlugin) as ConfigPlugin<IConfig>;
    this.host = configPlugin.get('bridgeMockServerHost');
  }

  public destroy() {}

  // my.xxx
  public async call<
    T extends keyof R,
    R extends Record<string, any> = BridgeMethods & SpecialMethods,
  >(
    api: T,
    params?: Parameters<R[T]>[0],
  ) {
    return this.host ? bridge.call(this.host, api, params) : super.call(api, params);
  }

  // my.call('xxx')
  public async mycall<R, P = Record<string, any>>(
    api: string,
    params?: P extends Record<string, any>
      ? P
      : Record<string, any> | ((...args: any[]) => void),
  ) {
    return this.host ? bridge.mycall(this.host, api, params) : super.mycall(api, params);
  }

  // my.ap.xxx
  public async ap<
    T extends keyof R,
    R extends Record<string, Fn> = APBridgeMethods
  >(
    api: T,
    params?: Parameters<R[T]>[0],
  ) {
    return this.host ? bridge.ap(this.host, api, params) : super.ap(api, params);
  }
}


public async init(getPlugin: GetPlugin) {
    const configPlugin = getPlugin(ConfigPlugin) as ConfigPlugin<IConfig>;
    this.host = configPlugin.get('bridgeMockServerHost');
  }


public destroy() {}


// my.xxx
public async call<
    T extends keyof R,
    R extends Record<string, any> = BridgeMethods & SpecialMethods,
  >(
    api: T,
    params?: Parameters<R[T]>[0],
  ) {
    return this.host ? bridge.call(this.host, api, params) : super.call(api, params);
  }


// my.call('xxx')
public async mycall<R, P = Record<string, any>>(
    api: string,
    params?: P extends Record<string, any>
      ? P
      : Record<string, any> | ((...args: any[]) => void),
  ) {
    return this.host ? bridge.mycall(this.host, api, params) : super.mycall(api, params);
  }


// my.ap.xxx
public async ap<
    T extends keyof R,
    R extends Record<string, Fn> = APBridgeMethods
  >(
    api: T,
    params?: Parameters<R[T]>[0],
  ) {
    return this.host ? bridge.ap(this.host, api, params) : super.ap(api, params);
  }


() => {
  test.each(testCases)(
    `given %o as query object,
    the result should be %o`,
    async (query: string, expectedResult: Record<string, number>) => {
      expect(makeSortQuery(query)).toEqual(expectedResult);
    }
  );
}


async (query: string, expectedResult: Record<string, number>) => {
      expect(makeSortQuery(query)).toEqual(expectedResult);
    }


() => {
  let service: EventsHubService;

  beforeEach(() => {
    TestBed.configureTestingModule({});
    service = TestBed.inject(EventsHubService);
  });

  it('should be created', () => {
    expect(service).toBeTruthy();
  });
}


() => {
    TestBed.configureTestingModule({});
    service = TestBed.inject(EventsHubService);
  }


export function plugin(initializerContext: PluginInitializerContext) {
  return new Plugin(initializerContext);
}


({ key, value }) => {
      if (this.ready) {
        this.storedData[key] = value;
        this.syncData(key, value);
      }
    }


@Module({
  name: 'GlobalStorage',
  deps: [{ dep: 'GlobalStorageOptions', optional: true }],
})
export class GlobalStorage extends StorageBase<Deps> {
  protected _storage: IStorage;
  protected _storageHandler: ({
    key,
    value,
  }: {
    key: string;
    value: unknown;
  }) => void = null;

  constructor(deps: Deps) {
    super(deps, {
      name: 'globalStorage',
      StorageProvider: deps.globalStorageOptions?.StorageProvider,
    });
  }

  storedData: Record<string, any> = {};

  onStateChange() {
    if (this.ready) {
      const currentData = this.data;
      // save new data to storage when changed
      for (const key in currentData) {
        if (this.storedData[key] !== currentData[key]) {
          this._storage.setItem(key, currentData[key]);
          this.storedData[key] = currentData[key];
        }
      }
    }
  }

  async onInit() {
    const storageKey = `${this.prefix ? `${this.prefix}-` : ''}GlobalStorage`;
    this._storage = new this._StorageProvider({
      storageKey,
    });
    this.storedData = await this._storage.getData();
    for (const key in this.storedData) {
      if (!this._storageReducers[key]) {
        delete this.storedData[key];
        await this._storage.removeItem(key);
      }
    }
    this.setData({
      ...this.data,
      ...this.storedData,
    });
    const currentData = this.data;
    for (const key in currentData) {
      if (!Object.prototype.hasOwnProperty.call(this.storedData, key)) {
        this._storage.setItem(key, currentData[key]);
      }
    }
  }

  onInitSuccess() {
    this._storageHandler = ({ key, value }) => {
      if (this.ready) {
        this.storedData[key] = value;
        this.syncData(key, value);
      }
    };
    this._storage.on('storage', this._storageHandler);
  }
}


onStateChange() {
    if (this.ready) {
      const currentData = this.data;
      // save new data to storage when changed
      for (const key in currentData) {
        if (this.storedData[key] !== currentData[key]) {
          this._storage.setItem(key, currentData[key]);
          this.storedData[key] = currentData[key];
        }
      }
    }
  }


async onInit() {
    const storageKey = `${this.prefix ? `${this.prefix}-` : ''}GlobalStorage`;
    this._storage = new this._StorageProvider({
      storageKey,
    });
    this.storedData = await this._storage.getData();
    for (const key in this.storedData) {
      if (!this._storageReducers[key]) {
        delete this.storedData[key];
        await this._storage.removeItem(key);
      }
    }
    this.setData({
      ...this.data,
      ...this.storedData,
    });
    const currentData = this.data;
    for (const key in currentData) {
      if (!Object.prototype.hasOwnProperty.call(this.storedData, key)) {
        this._storage.setItem(key, currentData[key]);
      }
    }
  }


onInitSuccess() {
    this._storageHandler = ({ key, value }) => {
      if (this.ready) {
        this.storedData[key] = value;
        this.syncData(key, value);
      }
    };
    this._storage.on('storage', this._storageHandler);
  }


async () => {
    console.log('Publisher successfully connected to NATS');

    const publisher = new TicketCreatedPublisher(client);

    try {
        await publisher.publish({
            id: '123',
            title: 'Aditya Test Concert Ticket',
            price: 20,
        });
    } catch (err) {
        console.error(err);
    }
}


export default class MenuControl extends React.Component<{
    selected?: boolean,
}> {
    public render() {
        const { selected } = this.props;
        return (
            <Moveable />);
    }
}


public render() {
        const { selected } = this.props;
        return (
            <Moveable />);
    }


function compileWithStringify(template: string) {
    return compile(template, {
      hoistStatic: true,
      prefixIdentifiers: true,
      transformHoist: stringifyStatic
    })
  }


function repeat(code: string, n: number): string {
    return new Array(n)
      .fill(0)
      .map(() => code)
      .join('')
  }


() => code


() => {
    const { ast } = compileWithStringify(
      `<div><div><div>hello</div><div>hello</div></div></div>`
    )
    expect(ast.hoists.length).toBe(1)
    // should be a normal vnode call
    expect(ast.hoists[0]!.type).toBe(NodeTypes.VNODE_CALL)
  }


() => {
    const { ast } = compileWithStringify(
      `

${repeat(
        `<span class="foo"/>`,
        StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
      )}

`
    )
    expect(ast.hoists.length).toBe(1)
    // should be optimized now
    expect(ast.hoists[0]).toMatchObject({
      type: NodeTypes.JS_CALL_EXPRESSION,
      callee: CREATE_STATIC,
      arguments: [
        JSON.stringify(
          `
${repeat(
            `<span class="foo"></span>`,
            StringifyThresholds.ELEMENT_WITH_BINDING_COUNT
          )}
`
        ),
        '1'
      ]
    })
  }


() => {
    const { ast } = compileWithStringify(
      `

${repeat(
        `<span/>`,
        StringifyThresholds.NODE_COUNT
      )}

`
    )
    expect(ast.hoists.length).toBe(1)
    // should be optimized now
    expect(ast.hoists[0]).toMatchObject({
      type: NodeTypes.JS_CALL_EXPRESSION,
      callee: CREATE_STATIC,
      arguments: [
        JSON.stringify(
          `
${repeat(
            `<span></span>`,
            StringifyThresholds.NODE_COUNT
          )}
`
        ),
        '1'
      ]
    })
  }