package com.jscape.inet.scp.protocol.marshaling;

import com.jscape.util.StringTools;

import java.io.IOException;
import java.io.StringReader;
import java.util.LinkedList;
import java.util.List;

/**
 * @author Alex Usun
 */
public class CommandLineFormat {

    // todo: use scanner
    public static CommandLine parse(String data)
            throws IOException {
        StringReader reader = new StringReader(data);

        String command = readWord(reader);
        assertCommandValid(command);

        List<CommandOption> options = new LinkedList<>();
        String path = null;
        String word;
        while (!eof(word = readWord(reader))) {
            if (isOption(word)) {
                parseOptions(word, options);
            } else if (path != null) {
                throw new IOException("Unexpected command line part: " + word);
            } else {
                path = parsePath(word, reader);
            }
        }

        assertHasOptions(options);

        if (path == null) {
            path = "";
        }

        return new CommandLine(path, options);
    }

    public static String format(CommandLine value, boolean formatOptionsSeparated) {
        String optionsLine = formatOptions(value.options, formatOptionsSeparated);
        String path = formatPath(value.path);

        return String.format("scp %s %s", optionsLine, path);
    }

    private static String readWord(StringReader reader)
            throws IOException {
        StringBuilder buf = new StringBuilder();
        int ch;

        while (!eof(ch = reader.read()) && Character.isWhitespace((char) ch)) {
            // ok
        }

        if (eof(ch)) {
            return null;
        }

        do {
            buf.append((char) ch);
            ch = reader.read();
        } while (!eof(ch) && !Character.isWhitespace((char) ch));

        return buf.length() > 0 ? buf.toString() : null;
    }

    private static boolean isOption(String word) {
        return word.startsWith("-");
    }

    private static void parseOptions(String line, List<CommandOption> options) {
        for (int i = 1; i < line.length(); ++i) {
            char optionValue = line.charAt(i);

            try {
                CommandOption option = CommandOption.optionFor(optionValue);
                options.add(option);
            } catch (Exception e) {
                // ignore unsupported option
            }
        }
    }

    private static String parsePath(String line, StringReader reader)
            throws IOException {
        StringBuilder buffer = new StringBuilder(line);
        buffer.append(' '); // space read by readWord()
        int ch;

        while (!eof(ch = reader.read())) {
            buffer.append((char) ch);
        }

        String path = buffer.toString().trim();

        return StringTools.strip(StringTools.strip(path, "\"", "\""), "'", "'");
    }

    private static void assertCommandValid(String command)
            throws IOException {
        if (!"scp".equalsIgnoreCase(command)) {
            throw new IOException("Unexpected command: " + command);
        }
    }

    private static void assertHasOptions(List<CommandOption> options)
            throws IOException {
        if (options.isEmpty()) {
            throw new IOException("Required options not present.");
        }
    }

    private static void assertHasPath(String path)
            throws IOException {
        if (path == null) {
            throw new IOException("Path not present.");
        }
    }

    private static boolean eof(int ch) {
        return ch == -1;
    }

    private static boolean eof(String line) {
        return line == null;
    }

    private static String formatOptions(List<CommandOption> options, boolean formatOptionsSeparated) {
        StringBuilder buf = new StringBuilder("-");
        int i = 0;

        for (CommandOption option : options) {
            if (i++ > 0 && formatOptionsSeparated) {
                buf.append(" -");
            }

            buf.append(option.value);
        }

        return buf.toString();
    }

    private static String formatPath(String path) {
        return path.contains(" ") ? "\"" + path + "\"" : path;
    }

    private CommandLineFormat() {
    }
}
