/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */
package org.apache.groovy.groovysh.jline

import org.jline.builtins.Commands
import org.jline.builtins.ConfigurationPath
import org.jline.builtins.Less
import org.jline.builtins.Nano
import org.jline.builtins.Options
import org.jline.console.CommandInput
import org.jline.console.CommandMethods
import org.jline.console.impl.Builtins
import org.jline.reader.LineReader
import org.jline.reader.Widget

import java.nio.file.Files
import java.nio.file.Path
import java.util.function.Function
import java.util.function.Supplier

/**
 * Registers console built-ins that integrate JLine commands with the Groovy shell buffer.
 */
class GroovyBuiltins extends Builtins {
    private final ConfigurationPath configPath
    private final Supplier<Path> workDir
    private final GroovyEngine engine

    /**
     * Creates the built-in command registry used by groovysh.
     *
     * @param engine shell engine that owns the current buffer
     * @param workDir supplier for the current working directory
     * @param configPath configuration lookup path
     * @param reader active line reader
     * @param widgetCreator widget factory used by the parent built-ins
     */
    GroovyBuiltins(GroovyEngine engine, Supplier<Path> workDir, ConfigurationPath configPath, LineReader reader, Function<String, Widget> widgetCreator) {
        super(workDir, configPath, widgetCreator)
        this.workDir = workDir
        this.configPath = configPath
        this.engine = engine
        // Builtins doesn't have the extension points we want, so duplicate its command registration here
        def commandExecute = commandNames().collectEntries { name ->
            def methods = getCommandMethods(name)
            if (name in ['nano', 'less']) {
                methods = new CommandMethods((Function)this::"$name", methods.compileCompleter())
            }
            [Command."${name.toUpperCase(Locale.ROOT)}", methods]
        }
        def commandName = commandNames().collectEntries{ name ->
            [Command."${name.toUpperCase(Locale.ROOT)}", '/' + name]
        }
        setLineReader(reader)
        registerCommands(commandName, commandExecute)
    }

    private void less(CommandInput input) {
        Options opt = Options.compile(*Less.usage()*.replaceAll('less ', '/less ')).parse(input.args())
        try {
            if (opt.isSet("help")) {
                throw new Options.HelpException(opt.usage())
            }
            boolean usingBuffer = opt.args().size() == 0
            if (usingBuffer) {
                def temp = Files.createTempFile('groovysh', '.groovy').toFile()
                temp.text = engine.buffer
                input = new CommandInput(input.command(), [*input.args(), temp.absolutePath] as String[], input.terminal(), input.in(), input.out(), input.err())
            }
            GroovyPosixCommands.less(new GroovyPosixContext(input.in(), new PrintStream(input.out()), new PrintStream(input.err()), workDir.get(), input.terminal(), engine::get, configPath), ['/less', *input.args()] as String[])
        } catch (Exception e) {
            saveException(e)
        }
    }

    private void nano(CommandInput input) {
        Options opt = Options.compile(*Nano.usage()*.replaceAll('nano ', '/nano ')).parse(input.args())
        try {
            if (opt.isSet("help")) {
                throw new Options.HelpException(opt.usage());
            }
            boolean usingBuffer = opt.args().size() == 0
            def temp = null
            if (usingBuffer) {
                temp = Files.createTempFile('groovysh', '.groovy').toFile()
                temp.text = engine.buffer
                input = new CommandInput(input.command(), [*input.args(), temp.absolutePath] as String[], input.terminal(), input.in(), input.out(), input.err())
            }
            Commands.nano(input.terminal(), input.out(), input.err(), workDir.get(), input.args(), configPath)
            if (temp) GroovyCommands.loadFile(engine, temp)
        } catch (Exception e) {
            saveException(e)
        }
    }

    /**
     * Returns the help-group name used for the built-in commands.
     *
     * @return the console command group name
     */
    @Override
    String name() {
        'Console Commands'
    }
}
