// Copyright (c) 2018, Compiler Explorer Authors
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//     * Redistributions of source code must retain the above copyright notice,
//       this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright
//       notice, this list of conditions and the following disclaimer in the
//       documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

import {assert} from './assert.js';
import {
    ClientState,
    ClientStateCompiler,
    ClientStateConformanceView,
    ClientStateExecutor,
    ClientStateSession,
    ClientStateTree,
} from './clientstate.js';

type BasicGoldenLayoutStruct = {
    type: string;
    width?: number;
    height?: number;
    isClosable?: boolean;
    reorderEnabled?: boolean;
    content: Array<BasicGoldenLayoutStruct | GoldenLayoutComponentStruct>;
};

type GoldenLayoutComponentStruct = {
    type: string;
    title?: string;
    componentName: string;
    componentState: any;
    isClosable: boolean;
    reorderEnabled: boolean;
};

export type GoldenLayoutRootStruct = {
    settings?: any;
    dimensions?: Record<string, number>;
    labels?: Record<string, string>;
    content?: Array<BasicGoldenLayoutStruct>;
};

export class ClientStateNormalizer {
    normalized = new ClientState();
    rootContent: Array<BasicGoldenLayoutStruct> | undefined;

    fromGoldenLayoutContent(content: BasicGoldenLayoutStruct[]) {
        if (!this.rootContent) this.rootContent = content;

        for (const component of content) {
            this.fromGoldenLayoutComponent(component);
        }
    }

    setFilterSettingsFromComponentState(compiler: ClientStateCompiler, componentState) {
        compiler.filters.binary = componentState.filters.binary;
        compiler.filters.binaryObject = componentState.filters.binaryObject;
        compiler.filters.execute = componentState.filters.execute;
        compiler.filters.labels = componentState.filters.labels;
        compiler.filters.libraryCode = componentState.filters.libraryCode;
        compiler.filters.directives = componentState.filters.directives;
        compiler.filters.commentOnly = componentState.filters.commentOnly;
        compiler.filters.trim = componentState.filters.trim;
        compiler.filters.intel = componentState.filters.intel;
        compiler.filters.demangle = componentState.filters.demangle;
        compiler.filters.debugCalls = componentState.filters.debugCalls;
    }

    // UNUSED, not deleting yet
    // setFilterSettingsFromComponent(compiler: ClientStateCompiler, component) {
    //     this.setFilterSettingsFromComponentState(compiler, component.componentState);
    // }

    findCompilerInGoldenLayout(
        content: Array<BasicGoldenLayoutStruct | GoldenLayoutComponentStruct>,
        id: number,
    ): GoldenLayoutComponentStruct | null {
        let result: GoldenLayoutComponentStruct | null = null;

        for (const component of content) {
            if ('componentName' in component && component.componentName === 'compiler') {
                if (component.componentState.id === id) {
                    return component;
                }
            } else if ('content' in component && component.content.length > 0) {
                result = this.findCompilerInGoldenLayout(component.content, id);
                if (result) break;
            }
        }

        return result;
    }

    // UNUSED. not deleting yet
    // findOrCreateSessionFromEditorOrCompiler(editorId, compilerId) {
    //     let session;
    //     if (editorId) {
    //         session = this.normalized.findOrCreateSession(editorId);
    //     } else {
    //         const glCompiler = this.findCompilerInGoldenLayout(this.rootContent, compilerId);
    //         if (glCompiler) {
    //             if (glCompiler.componentState.source) {
    //                 session = this.normalized.findOrCreateSession(glCompiler.componentState.source);
    //             }
    //         }
    //     }
    //     return session;
    // }

    addSpecialOutputToCompiler(compilerId: number, name: string, editorId: number) {
        const glCompiler = this.findCompilerInGoldenLayout(this.rootContent!, compilerId);
        if (glCompiler) {
            let compiler;
            if (glCompiler.componentState.source) {
                const session = this.normalized.findOrCreateSession(glCompiler.componentState.source);
                compiler = session.findOrCreateCompiler(compilerId);
            } else {
                assert(glCompiler.componentState.tree);
                const tree = this.normalized.findOrCreateTree(glCompiler.componentState.tree);
                compiler = tree.findOrCreateCompiler(compilerId);
            }

            compiler.specialoutputs.push(name);
        } else if (editorId) {
            const session = this.normalized.findOrCreateSession(editorId);
            const compiler = session.findOrCreateCompiler(compilerId);
            compiler.specialoutputs.push(name);
        }
    }

    addToolToCompiler(compilerId: number, toolId: string, args: string[], stdin: string) {
        const glCompiler = this.findCompilerInGoldenLayout(this.rootContent!, compilerId);
        if (glCompiler) {
            let compiler: ClientStateCompiler;
            if (glCompiler.componentState.source) {
                const session = this.normalized.findOrCreateSession(glCompiler.componentState.source);
                compiler = session.findOrCreateCompiler(compilerId);
            } else {
                assert(glCompiler.componentState.tree);
                const tree = this.normalized.findOrCreateTree(glCompiler.componentState.tree);
                compiler = tree.findOrCreateCompiler(compilerId);
            }

            compiler.tools.push({
                id: toolId,
                args: args,
                stdin: stdin,
            });
        }
    }

    addExecutorFromComponentState(componentState) {
        const executor = new ClientStateExecutor();
        executor.compiler.id = componentState.compiler;
        executor.compiler.options = componentState.options;
        executor.compiler.libs = componentState.libs;
        executor.compilerVisible = componentState.compilationPanelShown;
        executor.compilerOutputVisible = componentState.compilerOutShown;
        executor.arguments = componentState.execArgs;
        executor.argumentsVisible = componentState.argsPanelShown;
        executor.stdin = componentState.execStdin;
        executor.stdinVisible = componentState.stdinPanelShown;
        if (componentState.overrides) {
            executor.compiler.overrides = componentState.overrides;
        }
        if (componentState.runtimeTools) {
            executor.runtimeTools = componentState.runtimeTools;
        }
        if (componentState.wrap) executor.wrap = true;

        if (componentState.source) {
            const session = this.normalized.findOrCreateSession(componentState.source);

            session.executors.push(executor);
        } else if (componentState.tree) {
            const tree = this.normalized.findOrCreateTree(componentState.tree);

            tree.executors.push(executor);
        }
    }

    addCompilerFromComponentState(componentState: ClientStateCompiler) {
        let compiler;
        if (componentState.id) {
            if (componentState.source) {
                const session = this.normalized.findOrCreateSession(componentState.source);
                compiler = session.findOrCreateCompiler(componentState.id as number);
            } else if (componentState.tree) {
                const tree = this.normalized.findOrCreateTree(componentState.tree);
                compiler = tree.findOrCreateCompiler(componentState.id as number);
            } else {
                return;
            }
        } else {
            // Ofek: do we ever get here?

            compiler = new ClientStateCompiler();
            if (componentState.source) {
                const session = this.normalized.findOrCreateSession(componentState.source);
                session.compilers.push(compiler);

                this.normalized.numberCompilersIfNeeded(session, this.normalized.getNextCompilerId());
            } else if (componentState.tree) {
                const tree = this.normalized.findOrCreateTree(componentState.tree);
                tree.compilers.push(compiler);
            } else {
                return;
            }
        }

        // Ofek: id and compiler get mixed up in Compiler.getCurrentState?
        compiler.id = componentState.compiler;
        compiler.options = componentState.options;
        compiler.libs = componentState.libs;
        if (componentState.overrides) {
            compiler.overrides = componentState.overrides;
        }
        this.setFilterSettingsFromComponentState(compiler, componentState);
    }

    fromGoldenLayoutComponent(component: BasicGoldenLayoutStruct | GoldenLayoutComponentStruct) {
        if ('componentName' in component) {
            // handle GoldenLayoutComponentStruct
            switch (component.componentName) {
                case 'tree': {
                    const tree = this.normalized.findOrCreateTree(component.componentState.id);
                    tree.fromJsonData(component.componentState);
                    break;
                }
                case 'codeEditor': {
                    const session = this.normalized.findOrCreateSession(component.componentState.id);
                    session.language = component.componentState.lang;
                    session.source = component.componentState.source;
                    if (component.componentState.filename) session.filename = component.componentState.filename;
                    break;
                }
                case 'compiler': {
                    this.addCompilerFromComponentState(component.componentState);
                    break;
                }
                case 'executor': {
                    this.addExecutorFromComponentState(component.componentState);
                    break;
                }
                case 'ast': {
                    this.addSpecialOutputToCompiler(
                        component.componentState.id,
                        'ast',
                        component.componentState.editorid,
                    );
                    break;
                }
                case 'opt': {
                    this.addSpecialOutputToCompiler(
                        component.componentState.id,
                        'opt',
                        component.componentState.editorid,
                    );
                    break;
                }
                case 'stackusage': {
                    this.addSpecialOutputToCompiler(
                        component.componentState.id,
                        'stackusage',
                        component.componentState.editorid,
                    );
                    break;
                }
                case 'cfg': {
                    this.addSpecialOutputToCompiler(
                        component.componentState.id,
                        'cfg',
                        component.componentState.editorid,
                    );
                    break;
                }
                case 'gccdump': {
                    this.addSpecialOutputToCompiler(
                        component.componentState._compilerid,
                        'gccdump',
                        component.componentState._editorid,
                    );
                    break;
                }
                case 'output': {
                    this.addSpecialOutputToCompiler(
                        component.componentState.compiler,
                        'compilerOutput',
                        component.componentState.editor,
                    );
                    break;
                }
                case 'conformance': {
                    const session = this.normalized.findOrCreateSession(component.componentState.editorid);
                    session.conformanceview = new ClientStateConformanceView(component.componentState);
                    break;
                }
                case 'tool': {
                    this.addToolToCompiler(
                        component.componentState.id,
                        component.componentState.toolId,
                        component.componentState.args,
                        component.componentState.stdin,
                    );
                    break;
                }
            }
        } else if ('content' in component) {
            // handle BasicGoldenLayoutStruct
            this.fromGoldenLayoutContent(component.content as unknown as BasicGoldenLayoutStruct[]);
        }

        this.fixFilesInTrees();
    }

    fixFilesInTrees() {
        for (const tree of this.normalized.trees) {
            tree.files = tree.files.filter(file => file.isIncluded);

            for (const file of tree.files) {
                if (file.editorId) {
                    const session = this.normalized.findSessionById(file.editorId);
                    if (session) {
                        file.content = session.source;
                        if (!file.filename && session.filename) {
                            // it's fine if the session doesn't contain the filename, the filename in the tree is always leading
                            file.filename = session.filename;
                        }
                    }
                }
            }
        }
    }

    fromGoldenLayout(globj: GoldenLayoutRootStruct) {
        this.rootContent = globj.content;

        if (globj.content) {
            this.fromGoldenLayoutContent(globj.content);
        }
    }
}

class GoldenLayoutComponents {
    createSourceComponent(session: ClientStateSession, customSessionId?: number): GoldenLayoutComponentStruct {
        const editor = {
            type: 'component',
            componentName: 'codeEditor',
            componentState: {
                id: customSessionId || session.id,
                source: session.source,
                lang: session.language,
            },
            isClosable: true,
            reorderEnabled: true,
        };

        if (session.filename) {
            return {
                ...editor,
                title: session.filename,
                componentState: {
                    ...editor.componentState,
                    filename: session.filename,
                },
            };
        }
        return editor;
    }

    createTreeComponent(tree: ClientStateTree, customTreeId?: number): GoldenLayoutComponentStruct {
        const treeComponent = {
            type: 'component',
            componentName: 'tree',
            componentState: {
                id: tree.id,
                cmakeArgs: tree.cmakeArgs,
                customOutputFilename: tree.customOutputFilename,
                isCMakeProject: tree.isCMakeProject,
                compilerLanguageId: tree.compilerLanguageId,
                files: tree.files,
                newFileId: tree.newFileId,
            },
            isClosable: true,
            reorderEnabled: true,
        };

        if (customTreeId) {
            treeComponent.componentState.id = customTreeId;
        }

        return treeComponent;
    }

    createAstComponent(
        session: ClientStateSession | null,
        compilerIndex: number,
        customSessionId?: number,
    ): GoldenLayoutComponentStruct {
        return {
            type: 'component',
            componentName: 'ast',
            componentState: {
                id: compilerIndex,
                editorid: customSessionId || (session ? session.id : undefined),
            },
            isClosable: true,
            reorderEnabled: true,
        };
    }

    createOptComponent(
        session: ClientStateSession | null,
        compilerIndex: number,
        customSessionId?: number,
    ): GoldenLayoutComponentStruct {
        return {
            type: 'component',
            componentName: 'opt',
            componentState: {
                id: compilerIndex,
                editorid: customSessionId || (session ? session.id : undefined),
            },
            isClosable: true,
            reorderEnabled: true,
        };
    }
    createStackUsageComponent(
        session: ClientStateSession,
        compilerIndex: number,
        customSessionId?: number,
    ): GoldenLayoutComponentStruct {
        return {
            type: 'component',
            componentName: 'stackusage',
            componentState: {
                id: compilerIndex,
                editorid: customSessionId || (session ? session.id : undefined),
            },
            isClosable: true,
            reorderEnabled: true,
        };
    }

    createCfgComponent(
        session: ClientStateSession | null,
        compilerIndex: number,
        customSessionId?: number,
    ): GoldenLayoutComponentStruct {
        return {
            type: 'component',
            componentName: 'opt',
            componentState: {
                id: compilerIndex,
                editorid: customSessionId || (session ? session.id : undefined),
                options: {
                    navigation: false,
                    physics: false,
                },
            },
            isClosable: true,
            reorderEnabled: true,
        };
    }

    createGccDumpComponent(
        session: ClientStateSession | null,
        compilerIndex: number,
        customSessionId?: number,
    ): GoldenLayoutComponentStruct {
        return {
            type: 'component',
            componentName: 'gccdump',
            componentState: {
                _compilerid: compilerIndex,
                _editorid: customSessionId || (session ? session.id : undefined),
            },
            isClosable: true,
            reorderEnabled: true,
        };
    }

    createCompilerOutComponent(
        session: ClientStateSession | null,
        compilerIndex: number,
        customSessionId?: number,
    ): GoldenLayoutComponentStruct {
        return {
            type: 'component',
            componentName: 'output',
            componentState: {
                compiler: compilerIndex,
                editor: customSessionId || (session ? session.id : undefined),
                wrap: false,
                fontScale: 14,
            },
            isClosable: true,
            reorderEnabled: true,
        };
    }

    createToolComponent(
        session: ClientStateSession | null,
        compilerIndex: number,
        toolId: string,
        args: string[],
        stdin: string,
        customSessionId?,
    ): GoldenLayoutComponentStruct {
        return {
            type: 'component',
            componentName: 'tool',
            componentState: {
                editor: customSessionId || (session ? session.id : undefined),
                compiler: compilerIndex,
                toolId: toolId,
                args: args,
                stdin: stdin,
            },
            isClosable: true,
            reorderEnabled: true,
        };
    }

    createConformanceViewComponent(
        session: ClientStateSession,
        conformanceview: ClientStateConformanceView,
    ): GoldenLayoutComponentStruct {
        return {
            type: 'component',
            componentName: 'conformance',
            componentState: {
                editorid: session.id,
                langId: session.language,
                compilers: [],
                libs: conformanceview.libs,
            },
            isClosable: true,
            reorderEnabled: true,
        };
    }

    copyCompilerFilters(filters) {
        return {...filters};
    }

    createSourceCompilerComponent(
        session: ClientStateSession,
        compiler,
        customSessionId?: number,
        idxCompiler?: number,
    ) {
        return {
            type: 'component',
            componentName: 'compiler',
            componentState: {
                id: idxCompiler,
                compiler: compiler.id,
                source: customSessionId || session.id,
                options: compiler.options,
                filters: this.copyCompilerFilters(compiler.filters),
                libs: compiler.libs,
                lang: session.language,
                overrides: compiler.overrides,
            },
            isClosable: true,
            reorderEnabled: true,
        };
    }

    createCompilerComponentForTree(
        tree: ClientStateTree,
        compiler: ClientStateCompiler,
        customTreeId: number | null,
        idxCompiler?: number,
    ) {
        return {
            type: 'component',
            componentName: 'compiler',
            componentState: {
                id: idxCompiler,
                compiler: compiler.id,
                source: undefined,
                tree: customTreeId || tree.id,
                options: compiler.options,
                filters: this.copyCompilerFilters(compiler.filters),
                libs: compiler.libs,
                lang: tree.compilerLanguageId,
                overrides: compiler.overrides,
            },
            isClosable: true,
            reorderEnabled: true,
        };
    }

    createExecutorComponent(session: ClientStateSession, executor: ClientStateExecutor, customSessionId?: number) {
        return {
            type: 'component',
            componentName: 'executor',
            componentState: {
                compiler: executor.compiler.id,
                source: customSessionId || session.id,
                options: executor.compiler.options,
                execArgs: executor.arguments,
                execStdin: executor.stdin,
                libs: executor.compiler.libs,
                lang: session.language,
                compilationPanelShown: executor.compilerVisible,
                compilerOutShown: executor.compilerOutputVisible,
                argsPanelShown: executor.argumentsVisible,
                stdinPanelShown: executor.stdinVisible,
                wrap: executor.wrap,
                overrides: executor.overrides,
                runtimeTools: executor.runtimeTools,
            },
            isClosable: true,
            reorderEnabled: true,
        };
    }

    createExecutorComponentForTree(tree: ClientStateTree, executor: ClientStateExecutor, customTreeId?: number) {
        return {
            type: 'component',
            componentName: 'executor',
            componentState: {
                compiler: executor.compiler.id,
                source: undefined,
                tree: customTreeId || tree.id,
                options: executor.compiler.options,
                execArgs: executor.arguments,
                execStdin: executor.stdin,
                libs: executor.compiler.libs,
                lang: tree.compilerLanguageId,
                compilationPanelShown: executor.compilerVisible,
                compilerOutShown: executor.compilerOutputVisible,
                argsPanelShown: executor.argumentsVisible,
                stdinPanelShown: executor.stdinVisible,
                wrap: executor.wrap,
                overrides: executor.overrides,
                runtimeTools: executor.runtimeTools,
            },
            isClosable: true,
            reorderEnabled: true,
        };
    }

    // UNUSED. not deleting yet
    // createDiffComponent(left, right) {
    //     return {
    //         type: 'component',
    //         componentName: 'diff',
    //         componentState: {
    //             lhs: left,
    //             rhs: right,
    //             lhsdifftype: 0,
    //             rhsdifftype: 0,
    //             fontScale: 14,
    //         },
    //     };
    // }

    createSpecialOutputComponent(
        viewtype: string,
        session: ClientStateSession,
        idxCompiler: number,
        customSessionId?: number,
    ): GoldenLayoutComponentStruct {
        switch (viewtype) {
            case 'ast': {
                return this.createAstComponent(session, idxCompiler + 1, customSessionId);
            }
            case 'opt': {
                return this.createOptComponent(session, idxCompiler + 1, customSessionId);
            }
            case 'stackusage': {
                return this.createStackUsageComponent(session, idxCompiler + 1, customSessionId);
            }
            case 'cfg': {
                return this.createCfgComponent(session, idxCompiler + 1, customSessionId);
            }
            case 'gccdump': {
                return this.createGccDumpComponent(session, idxCompiler + 1, customSessionId);
            }
            case 'compilerOutput': {
                return this.createCompilerOutComponent(session, idxCompiler + 1, customSessionId);
            }
            default: {
                throw new Error(`Unknown viewtype for compiler(${idxCompiler + 1}) ${viewtype}`);
            }
        }
    }

    createSpecialOutputComponentForTreeCompiler(viewtype: string, idxCompiler: number): GoldenLayoutComponentStruct {
        switch (viewtype) {
            case 'ast': {
                return this.createAstComponent(null, idxCompiler + 1);
            }
            case 'opt': {
                return this.createOptComponent(null, idxCompiler + 1);
            }
            case 'stackusage': {
                return this.createOptComponent(null, idxCompiler + 1);
            }
            case 'cfg': {
                return this.createCfgComponent(null, idxCompiler + 1);
            }
            case 'gccdump': {
                return this.createGccDumpComponent(null, idxCompiler + 1);
            }
            case 'compilerOutput': {
                return this.createCompilerOutComponent(null, idxCompiler + 1);
            }
            default: {
                throw new Error(`Unknown viewtype (for tree compiler ${idxCompiler + 1}) ${viewtype}`);
            }
        }
    }

    createToolComponentForTreeCompiler(
        tree: ClientStateTree,
        compilerIndex: number,
        toolId: number,
        args: string,
        stdin: string,
    ): GoldenLayoutComponentStruct {
        return {
            type: 'component',
            componentName: 'tool',
            componentState: {
                tree: tree ? tree.id : undefined,
                compiler: compilerIndex,
                toolId: toolId,
                args: args,
                stdin: stdin,
            },
            isClosable: true,
            reorderEnabled: true,
        };
    }
}

export class ClientStateGoldenifier extends GoldenLayoutComponents {
    golden: GoldenLayoutRootStruct = {};

    newEmptyStack(width: number): BasicGoldenLayoutStruct {
        return {
            type: 'stack',
            width: width,
            content: [],
        };
    }

    newEmptyRow(height: number): BasicGoldenLayoutStruct {
        return {
            type: 'row',
            height: height,
            content: [],
        };
    }

    newEmptyColumn(): BasicGoldenLayoutStruct {
        return {
            type: 'column',
            content: [],
        };
    }

    newStackWithOneComponent(width: number, component: GoldenLayoutComponentStruct): BasicGoldenLayoutStruct {
        return {
            type: 'stack',
            width: width,
            content: [component],
        };
    }

    newTreeFromTree(tree: ClientStateTree, width: number): BasicGoldenLayoutStruct {
        return this.newStackWithOneComponent(width, this.createTreeComponent(tree));
    }

    newSourceStackFromSession(session: ClientStateSession, width: number): BasicGoldenLayoutStruct {
        return this.newStackWithOneComponent(width, this.createSourceComponent(session));
    }

    newAstStackFromCompiler(
        session: ClientStateSession,
        compilerIndex: number,
        width: number,
    ): BasicGoldenLayoutStruct {
        return this.newStackWithOneComponent(width, this.createAstComponent(session, compilerIndex));
    }

    newOptStackFromCompiler(
        session: ClientStateSession,
        compilerIndex: number,
        width: number,
    ): BasicGoldenLayoutStruct {
        return this.newStackWithOneComponent(width, this.createOptComponent(session, compilerIndex));
    }

    newCfgStackFromCompiler(
        session: ClientStateSession,
        compilerIndex: number,
        width: number,
    ): BasicGoldenLayoutStruct {
        return this.newStackWithOneComponent(width, this.createCfgComponent(session, compilerIndex));
    }

    // UNUSED. not deleting yet
    // newGccDumpStackFromCompiler(session, compilerIndex, width: number): BasicGoldenLayoutStruct {
    //     return this.newStackWithOneComponent(width, this.createGccDumpComponent(session, compilerIndex));
    // }

    // UNUSED. not deleting yet
    // newCompilerOutStackFromCompiler(session, compilerIndex, width: number): BasicGoldenLayoutStruct {
    //     return this.newStackWithOneComponent(width, this.createCompilerOutComponent(session, compilerIndex));
    // }

    newToolStackFromCompiler(
        session: ClientStateSession,
        compilerIndex: number,
        toolId: string,
        args: string[],
        stdin: string,
        width: number,
    ): BasicGoldenLayoutStruct {
        return this.newStackWithOneComponent(
            width,
            this.createToolComponent(session, compilerIndex, toolId, args, stdin),
        );
    }

    newConformanceViewStack(
        session: ClientStateSession,
        width: number,
        conformanceview: ClientStateConformanceView,
    ): BasicGoldenLayoutStruct {
        const component = this.createConformanceViewComponent(session, conformanceview);

        for (const compiler of conformanceview.compilers) {
            const compjson = {
                compilerId: compiler.id,
                options: compiler.options,
            };

            component.componentState.compilers.push(compjson);
        }

        return this.newStackWithOneComponent(width, component);
    }

    newCompilerStackFromSession(
        session: ClientStateSession,
        compiler: ClientStateCompiler,
        width: number,
    ): BasicGoldenLayoutStruct {
        return this.newStackWithOneComponent(
            width,
            this.createSourceCompilerComponent(session, compiler, undefined, compiler._internalid),
        );
    }

    newExecutorStackFromSession(
        session: ClientStateSession,
        executor: ClientStateExecutor,
        width: number,
    ): BasicGoldenLayoutStruct {
        return this.newStackWithOneComponent(width, this.createExecutorComponent(session, executor));
    }

    createSourceContentArray(state: ClientState, leftSession: number, rightSession: number): BasicGoldenLayoutStruct[] {
        if (leftSession === rightSession) {
            return [this.createPresentationModeComponents(state.sessions[leftSession], 1, 100)];
        }
        return [
            this.createPresentationModeComponents(state.sessions[leftSession], 1),
            this.createPresentationModeComponents(state.sessions[rightSession], 2),
        ];
    }

    getPresentationModeEmptyLayout() {
        return {
            settings: {
                hasHeaders: true,
                constrainDragToContainer: false,
                reorderEnabled: true,
                selectionEnabled: false,
                popoutWholeStack: false,
                blockedPopoutsThrowError: true,
                closePopoutsOnUnload: true,
                showPopoutIcon: false,
                showMaximiseIcon: true,
                showCloseIcon: false,
                responsiveMode: 'onload',
                tabOverlapAllowance: 0,
                reorderOnTabMenuClick: true,
                tabControlOffset: 10,
            },
            dimensions: {
                borderWidth: 5,
                borderGrabWidth: 15,
                minItemHeight: 10,
                minItemWidth: 10,
                headerHeight: 20,
                dragProxyWidth: 300,
                dragProxyHeight: 200,
            },
            labels: {
                close: 'close',
                maximise: 'maximise',
                minimise: 'minimise',
                popout: 'open in new window',
                popin: 'pop in',
                tabDropdown: 'additional tabs',
            },
            content: [
                {
                    type: 'column',
                    content: [] as any[],
                },
            ],
        };
    }

    getPresentationModeLayoutForSource(state: ClientState, sessionIdx: number) {
        const gl = this.getPresentationModeEmptyLayout();
        gl.content[0].content = [
            {
                type: 'column',
                width: 100,
                content: this.createSourceContentArray(state, sessionIdx, sessionIdx),
            },
        ];

        return gl;
    }

    closeAllEditors(tree: ClientStateTree) {
        for (const file of tree.files) {
            file.editorId = -1;
            file.isOpen = false;
        }
    }

    getPresentationModeLayoutForTree(state: ClientState, leftTreeIdx: number) {
        const gl = this.getPresentationModeEmptyLayout();
        const tree = state.trees[leftTreeIdx];
        const customTreeId = 1;

        this.closeAllEditors(tree);

        gl.content[0].content = [
            {
                type: 'column',
                width: 100,
                content: [
                    {
                        type: 'row',
                        height: 50,
                        content: [this.createTreeComponent(tree, customTreeId)],
                    },
                    {
                        type: 'row',
                        height: 50,
                        content: [
                            {
                                type: 'stack',
                                width: 100,
                                content: [],
                            },
                        ],
                    },
                ],
            },
        ];

        const stack = gl.content[0].content[0].content[1].content[0];

        for (let idxCompiler = 0; idxCompiler < tree.compilers.length; idxCompiler++) {
            const compiler = tree.compilers[idxCompiler];
            stack.content.push(this.createCompilerComponentForTree(tree, compiler, customTreeId, idxCompiler + 1));

            for (const viewtype of compiler.specialoutputs) {
                stack.content.push(this.createSpecialOutputComponentForTreeCompiler(viewtype, idxCompiler));
            }

            for (const tool of compiler.tools) {
                stack.content.push(
                    this.createToolComponentForTreeCompiler(tree, idxCompiler + 1, tool.id, tool.args, tool.stdin),
                );
            }
        }

        for (let idxExecutor = 0; idxExecutor < tree.executors.length; idxExecutor++) {
            stack.content.push(this.createExecutorComponentForTree(tree, tree.executors[idxExecutor], customTreeId));
        }

        return gl;
    }

    // UNUSED. not deleting yet
    // getPresentationModeLayoutForComparisonSlide(state, left, right) {
    //     const gl = this.getPresentationModeEmptyLayout();
    //     gl.content[0].content = [
    //         {
    //             type: 'row',
    //             height: 50,
    //             content: this.createSourceContentArray(state, left, right),
    //         },
    //         {
    //             type: 'row',
    //             height: 50,
    //             content: [
    //                 {
    //                     type: 'stack',
    //                     width: 100,
    //                     content: [this.createDiffComponent(left.compiler + 1, right.compiler + 1)],
    //                 },
    //             ],
    //         },
    //     ];

    //     return gl;
    // }

    createPresentationModeComponents(session: ClientStateSession, customSessionId?: number, customWidth?: number) {
        const stack = {
            type: 'stack',
            width: customWidth || 50,
            activeItemIndex: 0,
            content: [this.createSourceComponent(session, customSessionId)] as any[],
        };

        for (let idxCompiler = 0; idxCompiler < session.compilers.length; idxCompiler++) {
            const compiler = session.compilers[idxCompiler];
            stack.content.push(this.createSourceCompilerComponent(session, compiler, customSessionId));

            for (const viewtype of compiler.specialoutputs) {
                stack.content.push(this.createSpecialOutputComponent(viewtype, session, idxCompiler, customSessionId));
            }

            for (const tool of compiler.tools) {
                stack.content.push(
                    this.createToolComponent(null, idxCompiler + 1, tool.id, tool.args, tool.stdin, customSessionId),
                );
            }
        }

        for (let idxExecutor = 0; idxExecutor < session.executors.length; idxExecutor++) {
            stack.content.push(this.createExecutorComponent(session, session.executors[idxExecutor], customSessionId));
        }

        return stack;
    }

    generatePresentationModeMobileViewerSlides(state: ClientState) {
        const slides: any[] = [];

        if (state.trees.length > 0) {
            for (let idxTree = 0; idxTree < state.trees.length; idxTree++) {
                const gl = this.getPresentationModeLayoutForTree(state, idxTree);
                slides.push(gl);
            }

            return slides;
        }

        for (let idxSession = 0; idxSession < state.sessions.length; idxSession++) {
            const gl = this.getPresentationModeLayoutForSource(state, idxSession);
            slides.push(gl);
        }

        return slides;
    }

    treeLayoutFromClientstate(state: ClientState, leaveSomeSpace: boolean): BasicGoldenLayoutStruct | undefined {
        const firstTree = state.trees[0];
        const leftSide = this.newTreeFromTree(firstTree, 25);
        const middle = this.newEmptyStack(40);
        for (const session of state.sessions) {
            middle.content.push(this.createSourceComponent(session));
        }

        let rightSide;
        let contentRow;
        let extraRow;

        if (leaveSomeSpace) {
            contentRow = this.newEmptyRow(50);
            extraRow = this.newEmptyRow(50);
            rightSide = this.newEmptyColumn();
            rightSide.content.push(contentRow, extraRow);
        } else {
            rightSide = this.newEmptyStack(40);
            contentRow = this.newEmptyRow(100);
        }

        let idxCompiler = 0;
        for (const compiler of firstTree.compilers) {
            contentRow.content.push(this.createCompilerComponentForTree(firstTree, compiler, null, idxCompiler + 1));

            for (const specialOutput of compiler.specialoutputs) {
                contentRow.content.push(
                    this.createSpecialOutputComponentForTreeCompiler(specialOutput, idxCompiler + 1),
                );
            }

            idxCompiler++;
        }

        rightSide.content.push(contentRow);

        assert(this.golden.content);
        this.golden.content[0].content.push(leftSide, middle, rightSide);

        return extraRow;
    }

    hasEditorCompilersOrExecutors(state: ClientState) {
        for (const session of state.sessions) {
            if (session.compilers.length > 0 || session.executors.length > 0) {
                return true;
            }
        }

        return false;
    }

    fromClientState(state: ClientState) {
        this.golden = {
            settings: {
                hasHeaders: true,
                constrainDragToContainer: false,
                reorderEnabled: true,
                selectionEnabled: false,
                popoutWholeStack: false,
                blockedPopoutsThrowError: true,
                closePopoutsOnUnload: true,
                showPopoutIcon: false,
                showMaximiseIcon: true,
                showCloseIcon: true,
                responsiveMode: 'onload',
                tabOverlapAllowance: 0,
                reorderOnTabMenuClick: true,
                tabControlOffset: 10,
            },
            dimensions: {
                borderWidth: 5,
                borderGrabWidth: 15,
                minItemHeight: 10,
                minItemWidth: 10,
                headerHeight: 20,
                dragProxyWidth: 300,
                dragProxyHeight: 200,
            },
            labels: {
                close: 'close',
                maximise: 'maximise',
                minimise: 'minimise',
                popout: 'open in new window',
                popin: 'pop in',
                tabDropdown: 'additional tabs',
            },
            content: [
                {
                    type: 'row',
                    content: [],
                },
            ],
        };

        if (state.trees.length > 0) {
            const hasOtherPanes = this.hasEditorCompilersOrExecutors(state);
            const extraRow = this.treeLayoutFromClientstate(state, hasOtherPanes);

            if (!hasOtherPanes || !extraRow) return;

            const treeCompilerCount = state.trees[0].compilers.length;
            const treeExecutorCount = state.trees[0].executors.length;

            for (let idxSession = 0; idxSession < state.sessions.length; idxSession++) {
                const session = state.sessions[idxSession];

                if (session.compilers.length > 0 || session.executors.length > 0) {
                    const rightCol: BasicGoldenLayoutStruct = {
                        type: 'column',
                        content: [],
                    };
                    const rightStack = this.newEmptyStack(100);

                    for (let idxCompiler = 0; idxCompiler < session.compilers.length; idxCompiler++) {
                        const compiler = session.compilers[idxCompiler];
                        const compilerComponent = this.createSourceCompilerComponent(
                            session,
                            compiler,
                            undefined,
                            treeCompilerCount + idxCompiler + 1,
                        );
                        rightStack.content.push(compilerComponent);

                        for (const viewtype of compiler.specialoutputs) {
                            rightStack.content.push(
                                this.createSpecialOutputComponent(
                                    viewtype,
                                    session,
                                    treeCompilerCount + idxCompiler + 1,
                                ),
                            );
                        }

                        for (const tool of compiler.tools) {
                            rightStack.content.push(
                                this.createToolComponent(
                                    session,
                                    treeCompilerCount + idxCompiler + 1,
                                    tool.id,
                                    tool.args,
                                    tool.stdin,
                                ),
                            );
                        }
                    }

                    for (let idxExecutor = 0; idxExecutor < session.executors.length; idxExecutor++) {
                        const executor = session.executors[idxExecutor];
                        const executorComponent = this.createExecutorComponent(
                            session,
                            executor,
                            treeExecutorCount + idxExecutor + 1,
                        );
                        rightStack.content.push(executorComponent);
                    }

                    rightCol.content.push(rightStack);

                    extraRow.content.push({
                        type: 'row',
                        content: [rightCol],
                    });
                }
            }

            return;
        }

        assert(this.golden.content);

        if (state.sessions.length > 1) {
            const sessionWidth = 100 / state.sessions.length;

            for (let idxSession = 0; idxSession < state.sessions.length; idxSession++) {
                const session = state.sessions[idxSession];

                const topRow: BasicGoldenLayoutStruct = {
                    type: 'row',
                    content: [],
                };
                const bottomRow: BasicGoldenLayoutStruct = {
                    type: 'row',
                    content: [],
                };

                const stack = this.newSourceStackFromSession(session, 100);
                topRow.content.push(stack);

                const compilerWidth =
                    100 /
                    (1 +
                        session.compilers.length +
                        session.executors.length +
                        session.countNumberOfSpecialOutputsAndTools());

                if (session.conformanceview) {
                    const stack = this.newConformanceViewStack(session, compilerWidth, session.conformanceview);
                    bottomRow.content.push(stack);
                }

                for (let idxCompiler = 0; idxCompiler < session.compilers.length; idxCompiler++) {
                    const compiler = session.compilers[idxCompiler];
                    const stack = this.newCompilerStackFromSession(session, compiler, compilerWidth);
                    bottomRow.content.push(stack);

                    for (const viewtype of compiler.specialoutputs) {
                        const stack = this.newStackWithOneComponent(
                            compilerWidth,
                            this.createSpecialOutputComponent(viewtype, session, idxCompiler),
                        );

                        if (stack) {
                            bottomRow.content.push(stack);
                        }
                    }

                    for (const tool of compiler.tools) {
                        const stack = this.newToolStackFromCompiler(
                            session,
                            idxCompiler + 1,
                            tool.id,
                            tool.args,
                            tool.stdin,
                            compilerWidth,
                        );
                        bottomRow.content.push(stack);
                    }
                }

                for (let idxExecutor = 0; idxExecutor < session.executors.length; idxExecutor++) {
                    const executor = session.executors[idxExecutor];
                    const stack = this.newExecutorStackFromSession(session, executor, compilerWidth);
                    bottomRow.content.push(stack);
                }

                const sessionColumn: BasicGoldenLayoutStruct = {
                    type: 'column',
                    isClosable: true,
                    reorderEnabled: true,
                    width: sessionWidth,
                    content: [topRow, bottomRow],
                };

                this.golden.content[0].content[idxSession] = sessionColumn;
            }
        } else if (state.sessions.length === 1) {
            const session = state.sessions[0];
            this.golden.content[0] = {
                type: 'row',
                content: [],
            };

            const width =
                100 /
                (1 +
                    session.compilers.length +
                    session.executors.length +
                    session.countNumberOfSpecialOutputsAndTools());
            this.golden.content[0].content.push(this.newSourceStackFromSession(session, width));

            if (session.conformanceview) {
                const stack = this.newConformanceViewStack(session, width, session.conformanceview);
                this.golden.content[0].content.push(stack);
            }

            for (let idxCompiler = 0; idxCompiler < session.compilers.length; idxCompiler++) {
                const compiler = session.compilers[idxCompiler];
                const stack = this.newCompilerStackFromSession(session, compiler, width);
                this.golden.content[0].content.push(stack);

                for (const viewtype of compiler.specialoutputs) {
                    const stack = this.newStackWithOneComponent(
                        width,
                        this.createSpecialOutputComponent(viewtype, session, idxCompiler),
                    );

                    if (stack) {
                        this.golden.content[0].content.push(stack);
                    }
                }

                for (const tool of compiler.tools) {
                    const stack = this.newToolStackFromCompiler(
                        session,
                        idxCompiler + 1,
                        tool.id,
                        tool.args,
                        tool.stdin,
                        width,
                    );
                    this.golden.content[0].content.push(stack);
                }
            }

            for (const executor of session.executors) {
                const stack = this.newExecutorStackFromSession(session, executor, width);
                this.golden.content[0].content.push(stack);
            }
        }
    }
}
