// 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.
"use strict";

let ClientState = require('./clientstate');

class ClientStateNormalizer {
    constructor() {
        this.normalized = new ClientState.State();
    }

    fromGoldenLayoutContent(content) {
        content.forEach((component) => {
            this.fromGoldenLayoutComponent(component);
        });
    }

    fromGoldenLayoutComponent(component) {
        if (component.componentName === "codeEditor") {
            const session = this.normalized.findOrCreateSession(component.componentState.id);
            session.language = component.componentState.lang;
            session.source = component.componentState.source;
        } else if (component.componentName === "compiler") {
            const session = this.normalized.findOrCreateSession(component.componentState.source);

            const compiler = new ClientState.Compiler();
            compiler.id = component.componentState.compiler;
            compiler.options = component.componentState.options;
            compiler.libs = component.componentState.libs;
            compiler.filters.binary = component.componentState.filters.binary;
            compiler.filters.labels = component.componentState.filters.labels;
            compiler.filters.directives = component.componentState.filters.directives;
            compiler.filters.commentOnly = component.componentState.filters.commentOnly;
            compiler.filters.trim = component.componentState.filters.trim;
            compiler.filters.intel = component.componentState.filters.intel;
            compiler.filters.demangle = component.componentState.filters.demangle;

            session.compilers.push(compiler);
        } else if (component.componentName === "executor") {
            const session = this.normalized.findOrCreateSession(component.componentState.source);

            const executor = new ClientState.Executor();
            executor.compiler.id = component.componentState.compiler;
            executor.compiler.options = component.componentState.options;
            executor.compiler.libs = component.componentState.libs;
            executor.compilerVisible = component.componentState.compilationPanelShown;
            executor.compilerOutputVisible = component.componentState.compilerOutShown;
            executor.arguments = component.componentState.execArgs;
            executor.argumentsVisible = component.componentState.argsPanelShown;
            executor.stdin = component.componentState.execStdin;
            executor.stdinVisible = component.componentState.stdinPanelShown;

            session.executors.push(executor);
        } else if (component.componentName === "ast") {
            const session = this.normalized.findOrCreateSession(component.componentState.editorid);
            const compiler = session.findOrCreateCompiler(component.componentState.id);

            compiler.specialoutputs.push("ast");
        } else if (component.componentName === "opt") {
            const session = this.normalized.findOrCreateSession(component.componentState.editorid);
            const compiler = session.findOrCreateCompiler(component.componentState.id);

            compiler.specialoutputs.push("opt");
        } else if (component.componentName === "cfg") {
            const session = this.normalized.findOrCreateSession(component.componentState.editorid);
            const compiler = session.findOrCreateCompiler(component.componentState.id);

            compiler.specialoutputs.push("cfg");
        } else if (component.componentName === "gccdump") {
            const session = this.normalized.findOrCreateSession(component.componentState._editorid);
            const compiler = session.findOrCreateCompiler(component.componentState._compilerid);

            compiler.specialoutputs.push("gccdump");
        } else if (component.componentName === "conformance") {
            const session = this.normalized.findOrCreateSession(component.componentState.editorid);
            session.conformanceview = new ClientState.ConformanceView(component.componentState);
        } else if (component.componentName === "tool") {
            const session = this.normalized.findOrCreateSession(component.componentState.editor);
            const compiler = session.findOrCreateCompiler(component.componentState.compiler);

            compiler.tools.push({
                id: component.componentState.toolId,
                args: component.componentState.args
            });
        } else if (component.content) {
            this.fromGoldenLayoutContent(component.content);
        }
    }

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

class ClientStateGoldenifier {
    constructor() {
        this.golden = {};
    }

    newStackWithOneComponent(width, component) {
        return {
            type: "stack",
            width: width,
            content: [component]
        };
    }

    newSourceStackFromSession(session, width) {
        return this.newStackWithOneComponent(width,
            {
                type: "component",
                componentName: "codeEditor",
                componentState: {
                    id: session.id,
                    source: session.source,
                    lang: session.language,
                },
                isClosable: true,
                reorderEnabled: true
            }
        );
    }

    newAstStackFromCompiler(session, compiler, compilerIndex, width) {
        return this.newStackWithOneComponent(width,
            {
                type: "component",
                componentName: "ast",
                componentState: {
                    id: compilerIndex,
                    editorid: session.id
                },
                isClosable: true,
                reorderEnabled: true
            }
        );
    }

    newOptStackFromCompiler(session, compiler, compilerIndex, width) {
        return this.newStackWithOneComponent(width,
            {
                type: "component",
                componentName: "opt",
                componentState: {
                    id: compilerIndex,
                    editorid: session.id
                },
                isClosable: true,
                reorderEnabled: true
            }
        );
    }

    newCfgStackFromCompiler(session, compiler, compilerIndex, width) {
        return this.newStackWithOneComponent(width,
            {
                type: "component",
                componentName: "opt",
                componentState: {
                    id: compilerIndex,
                    editorid: session.id,
                    options: {
                        navigation: false,
                        physics: false
                    }
                },
                isClosable: true,
                reorderEnabled: true
            }
        );
    }

    newGccDumpStackFromCompiler(session, compiler, compilerIndex, width) {
        return this.newStackWithOneComponent(width,
            {
                type: "component",
                componentName: "gccdump",
                componentState: {
                    _compilerid: compilerIndex,
                    _editorid: session.id
                },
                isClosable: true,
                reorderEnabled: true
            }
        );
    }

    newToolStackFromCompiler(session, compiler, compilerIndex, toolId, args, width) {
        return this.newStackWithOneComponent(width,
            {
                type: "component",
                componentName: "tool",
                componentState: {
                    editor: session.id,
                    compiler: compilerIndex,
                    toolId: toolId,
                    args: args
                },
                isClosable: true,
                reorderEnabled: true
            }
        );
    }

    newConformanceViewStack(session, width, conformanceview) {
        const stack = this.newStackWithOneComponent(width,
            {
                type: "component",
                componentName: "conformance",
                componentState: {
                    editorid: session.id,
                    langId: session.language,
                    compilers: [],
                    libs: conformanceview.libs
                },
                isClosable: true,
                reorderEnabled: true
            }
        );

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

            stack.content[0].componentState.compilers.push(compjson);
        });

        return stack;
    }

    newCompilerStackFromSession(session, compiler, width) {
        return this.newStackWithOneComponent(width,
            {
                type: "component",
                componentName: "compiler",
                componentState: {
                    compiler: compiler.id,
                    source: session.id,
                    options: compiler.options,
                    filters: {
                        binary: compiler.filters.binary,
                        execute: compiler.filters.execute,
                        labels: compiler.filters.labels,
                        directives: compiler.filters.directives,
                        commentOnly: compiler.filters.commentOnly,
                        trim: compiler.filters.trim,
                        intel: compiler.filters.intel,
                        demangle: compiler.filters.demangle
                    },
                    libs: compiler.libs,
                    lang: session.language
                },
                isClosable: true,
                reorderEnabled: true
            }
        );
    }

    newExecutorStackFromSession(session, executor, width) {
        return this.newStackWithOneComponent(width,
            {
                type: "component",
                componentName: "executor",
                componentState: {
                    compiler: executor.compiler.id,
                    source: 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
                },
                isClosable: true,
                reorderEnabled: true
            }
        );
    }

    newSpecialOutputStack(viewtype, session, compiler, idxCompiler, compilerWidth) {
        let stack = null;
        if (viewtype === "ast") {
            stack = this.newAstStackFromCompiler(session, compiler, idxCompiler+1, compilerWidth);
        } else if (viewtype === "opt") {
            stack = this.newOptStackFromCompiler(session, compiler, idxCompiler+1, compilerWidth);
        } else if (viewtype === "cfg") {
            stack = this.newCfgStackFromCompiler(session, compiler, idxCompiler+1, compilerWidth);
        } else if (viewtype === "gccdump") {
            stack = this.newGccDumpStackFromCompiler(session, compiler, idxCompiler+1, compilerWidth);
        }

        return stack;
    }

    fromClientState(state) {
        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.sessions.length > 1) {
            const sessionWidth = 100.0 / state.sessions.length;
                
            for (let idxSession = 0; idxSession < state.sessions.length; idxSession++) {
                const session = state.sessions[idxSession];

                this.golden.content[0].content[idxSession] = {
                    type: "column",
                    isClosable: true,
                    reorderEnabled: true,
                    width: sessionWidth,
                    content: [
                        {
                            type: "row",
                            content: [
                            ]
                        },
                        {
                            type: "row",
                            content: [
                            ]
                        }
                    ]
                };
    
                let stack = this.newSourceStackFromSession(session, 100.0);
                this.golden.content[0].content[idxSession].content[0].content.push(stack);

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

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

                for (let idxCompiler = 0; idxCompiler < session.compilers.length; idxCompiler++) {
                    const compiler = session.compilers[idxCompiler];
                    let stack = this.newCompilerStackFromSession(session, compiler, compilerWidth);
                    this.golden.content[0].content[idxSession].content[1].content.push(stack);
    
                    compiler.specialoutputs.forEach((viewtype) => {
                        let stack = this.newSpecialOutputStack(viewtype, session, compiler, idxCompiler, compilerWidth);
    
                        if (stack) {
                            this.golden.content[0].content[idxSession].content[1].content.push(stack);
                        }
                    });

                    compiler.tools.forEach((tool) => {
                        let stack = this.newToolStackFromCompiler(session, compiler, idxCompiler + 1,
                            tool.id, tool.args, compilerWidth);
                        this.golden.content[0].content[idxSession].content[1].content.push(stack);
                    });
                }

                for (let idxExecutor = 0; idxExecutor < session.executors.length; idxExecutor++) {
                    const executor = session.executors[idxExecutor];
                    let stack = this.newExecutorStackFromSession(session, executor, compilerWidth);
                    this.golden.content[0].content[idxSession].content[1].content.push(stack);
                }
            }
        } else if (state.sessions.length === 1) {
            const session = state.sessions[0];
            this.golden.content[0] = {
                type: "row",
                content: [
                ]
            };

            const width = 100.0 / (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];
                let stack = this.newCompilerStackFromSession(session, compiler, width);
                this.golden.content[0].content.push(stack);

                compiler.specialoutputs.forEach((viewtype) => {
                    let stack = this.newSpecialOutputStack(viewtype, session, compiler, idxCompiler, width);

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

                compiler.tools.forEach((tool) => {
                    let stack = this.newToolStackFromCompiler(session, compiler, idxCompiler + 1,
                        tool.id, tool.args, width);
                    this.golden.content[0].content.push(stack);
                });
            }

            session.executors.forEach((executor) => {
                let stack = this.newExecutorStackFromSession(session, executor, width);
                this.golden.content[0].content.push(stack);
            });
        }
    }
}

module.exports = {
    ClientStateNormalizer: ClientStateNormalizer,
    ClientStateGoldenifier: ClientStateGoldenifier
};
