// Copyright (c) 2023, 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.

/* eslint-disable unicorn/prefer-top-level-await */
/* eslint-disable no-console */

import nopt from 'nopt';
import _ from 'underscore';

import {CompilerArguments} from './lib/compiler-arguments.js';
import * as Parsers from './lib/compilers/argument-parsers.js';
import {executeDirect} from './lib/exec.js';
import {logger} from './lib/logger.js';

const opts = nopt({
    parser: [String],
    exe: [String],
    padding: [Number],
    debug: [Boolean],
});

if (opts.debug) logger.level = 'debug';

const compilerParsers = {
    gcc: Parsers.GCCParser,
    clang: Parsers.ClangParser,
    ldc: Parsers.LDCParser,
    erlang: Parsers.ErlangParser,
    pascal: Parsers.PascalParser,
    ispc: Parsers.ISPCParser,
    java: Parsers.JavaParser,
    kotlin: Parsers.KotlinParser,
    scala: Parsers.ScalaParser,
    vc: Parsers.VCParser,
    rust: Parsers.RustParser,
    mrustc: Parsers.MrustcParser,
    num: Parsers.NimParser,
    crystal: Parsers.CrystalParser,
    ts: Parsers.TypeScriptNativeParser,
    turboc: Parsers.TurboCParser,
    toit: Parsers.ToitParser,
    circle: Parsers.CircleParser,
    ghc: Parsers.GHCParser,
    tendra: Parsers.TendraParser,
    golang: Parsers.GolangParser,
};

class CompilerArgsApp {
    parserName: string;
    executable: string;
    compiler: any;
    pad: number;

    constructor() {
        this.parserName = opts.parser;
        this.executable = opts.exe;
        this.pad = opts.padding || 40;
        this.compiler = {
            compiler: {
                exe: this.executable,
            },
            possibleArguments: new CompilerArguments('some-id'),
            execCompilerCached: async (command: string, args: string[]) => {
                return executeDirect(command, args, {}, fn => fn);
            },
        };

        if (this.parserName === 'juliawrapper') {
            this.compiler.compilerWrapperPath = 'etc/scripts/julia_wrapper.jl';
        }
    }

    async getPossibleStdvers() {
        const parser = this.getParser();
        return await parser.getPossibleStdvers(this.compiler);
    }

    async getPossibleTargets() {
        const parser = this.getParser();
        return await parser.getPossibleTargets(this.compiler);
    }

    async getPossibleEditions() {
        const parser = this.getParser();
        return await parser.getPossibleEditions(this.compiler);
    }

    getParser() {
        if (compilerParsers[this.parserName as keyof typeof compilerParsers]) {
            return compilerParsers[this.parserName as keyof typeof compilerParsers];
        } else {
            console.error('Unknown parser type');
            process.exit(1);
        }
    }

    async doTheParsing() {
        const parser = this.getParser();
        await parser.parse(this.compiler);
        const options = this.compiler.possibleArguments.possibleArguments;
        if (parser.hasSupportStartsWith(options, '--target=')) {
            console.log('supportsTargetIs');
        } else if (parser.hasSupportStartsWith(options, '--target ')) {
            console.log('supportsTarget');
        } else if (parser.hasSupportStartsWith(options, '--march=')) {
            console.log('supportsMarch');
        } else {
            console.log('none of the things?');
        }
    }

    async print() {
        const args = _.keys(this.compiler.possibleArguments.possibleArguments);
        for (const arg of args) {
            const description = this.compiler.possibleArguments.possibleArguments[arg].description;
            console.log(`${arg.padEnd(this.pad, ' ')} ${description}`);
        }

        console.log('Stdvers:');
        console.log(await this.getPossibleStdvers());
        console.log('Targets:');
        console.log(await this.getPossibleTargets());
        console.log('Editions:');
        console.log(await this.getPossibleEditions());
    }
}

if (!opts.parser || !opts.exe) {
    console.error(
        'Usage: ' +
            'node --no-warnings=ExperimentalWarning --import=tsx compiler-args-app.ts ' +
            '--parser=<compilertype> --exe=<path> [--padding=<number>]\n' +
            'for example: --parser=clang --exe=/opt/compiler-explorer/clang-15.0.0/bin/clang++ --padding=50',
    );
    process.exit(1);
} else {
    const app = new CompilerArgsApp();
    app.doTheParsing()
        .then(() => app.print())
        .catch(e => console.error(e));
}
