<commit_before>package io.dockstore.client.cli;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;

import javax.ws.rs.ProcessingException;

import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.http.HttpStatus;

import com.esotericsoftware.yamlbeans.YamlReader;
import com.google.common.base.Joiner;
import com.google.gson.Gson;

import io.dockstore.common.CWL;
import io.swagger.client.ApiClient;
import io.swagger.client.ApiException;
import io.swagger.client.Configuration;
import io.swagger.client.api.ContainersApi;
import io.swagger.client.api.UsersApi;
import io.swagger.client.model.Container;
import io.swagger.client.model.Container.ModeEnum;
import io.swagger.client.model.Container.RegistryEnum;
import io.swagger.client.model.RegisterRequest;
import io.swagger.client.model.SourceFile;
import io.swagger.client.model.Tag;
import io.swagger.client.model.User;
import javassist.NotFoundException;

/*
 * Copyright (C) 2015 Collaboratory
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @author xliu
 *
 */
public class Client {

    private static ContainersApi containersApi;
    private static UsersApi usersApi;
    private static User user;
    private static CWL cwl;

    private static final String NAME_HEADER = "NAME";
    private static final String DESCRIPTION_HEADER = "DESCRIPTION";
    private static final String GIT_HEADER = "Git Repo";

    private static final int PADDING = 3;
    private static final int MAX_DESCRIPTION = 50;

    public static final int GENERIC_ERROR = 1;
    public static final int CONNECTION_ERROR = 150;

    private static void out(String format, Object... args) {
        System.out.println(String.format(format, args));
    }

    private static void out(String arg) {
        System.out.println(arg);
    }

    private static void err(String format, Object... args) {
        System.err.println(String.format(format, args));
    }

    public static final AtomicBoolean DEBUG = new AtomicBoolean(false);

    private static boolean isHelp(List<String> args, boolean valOnEmpty) {
        if (args.isEmpty()) {
            return valOnEmpty;
        }

        String first = args.get(0);
        return isHelpRequest(first);
    }

    private static class Kill extends RuntimeException {
    }

    private static void kill(String format, Object... args) {
        err(format, args);
        throw new Kill();
    }

    private static void invalid(String cmd) {
        kill("dockstore: '%s' is not a dockstore command. See 'dockstore --help'.", cmd);
    }

    private static void invalid(String cmd, String sub) {
        kill("dockstore: '%s %s' is not a dockstore command. See 'dockstore %s --help'.", cmd, sub, cmd);
    }

    private static boolean flag(List<String> args, String flag) {
        boolean found = false;
        for (int i = 0; i < args.size(); i++) {
            if (flag.equals(args.get(i))) {
                if (found) {
                    kill("consonance: multiple instances of '%s'.", flag);
                } else {
                    found = true;
                    args.remove(i);
                }
            }
        }
        return found;
    }

    private static String boolWord(boolean bool) {
        return bool ? "Yes" : "No";
    }

    private static List<String> optVals(List<String> args, String key) {
        List<String> vals = new ArrayList<>();

        for (int i = 0; i < args.size(); /** do nothing */
        i = i) {
            String s = args.get(i);
            if (key.equals(s)) {
                args.remove(i);
                if (i < args.size()) {
                    String val = args.remove(i);
                    if (!val.startsWith("--")) {
                        String[] ss = val.split(",");
                        if (ss.length > 0) {
                            vals.addAll(Arrays.asList(ss));
                            continue;
                        }
                    }
                }
                kill("dockstore: missing required argument to '%s'.", key);
            } else {
                i++;
            }
        }

        return vals;
    }

    private static String optVal(List<String> args, String key, String defaultVal) {
        String val = defaultVal;

        List<String> vals = optVals(args, key);
        if (vals.size() == 1) {
            val = vals.get(0);
        } else if (vals.size() > 1) {
            kill("dockstore: multiple instances of '%s'.", key);
        }

        return val;
    }

    private static String reqVal(List<String> args, String key) {
        String val = optVal(args, key, null);

        if (val == null) {
            kill("dockstore: missing required flag '%s'.", key);
        }

        return val;
    }

    private static int[] columnWidths(List<Container> containers) {
        int[] maxWidths = { NAME_HEADER.length(), DESCRIPTION_HEADER.length(), GIT_HEADER.length() };

        for (Container container : containers) {
            final String toolPath = container.getToolPath();
            if (toolPath != null && toolPath.length() > maxWidths[0]) {
                maxWidths[0] = toolPath.length();
            }
            final String description = container.getDescription();
            if (description != null && description.length() > maxWidths[1]) {
                maxWidths[1] = description.length();
            }
            final String gitUrl = container.getGitUrl();
            if (gitUrl != null && gitUrl.length() > maxWidths[2]) {
                maxWidths[2] = gitUrl.length();
            }
        }

        maxWidths[1] = (maxWidths[1] > MAX_DESCRIPTION) ? MAX_DESCRIPTION : maxWidths[1];

        return maxWidths;
    }

    private static class ContainerComparator implements Comparator<Container> {
        @Override
        public int compare(Container c1, Container c2) {
            String path1 = c1.getPath();
            String path2 = c2.getPath();

            return path1.compareToIgnoreCase(path2);
        }
    }

    private static void printContainerList(List<Container> containers) {
        Collections.sort(containers, new ContainerComparator());

        int[] maxWidths = columnWidths(containers);

        int nameWidth = maxWidths[0] + PADDING;
        int descWidth = maxWidths[1] + PADDING;
        int gitWidth = maxWidths[2] + PADDING;
        String format = "%-" + nameWidth + "s%-" + descWidth + "s%-" + gitWidth + "s%-16s%-16s%-10s";
        out(format, NAME_HEADER, DESCRIPTION_HEADER, GIT_HEADER, "On Dockstore?", "Dockstore.cwl", "Automated");

        for (Container container : containers) {
            String cwl = "No";
            String automated = "No";
            String description = "";
            String gitUrl = "";

            if (container.getValidTrigger()) {
                cwl = "Yes";
            }

            if (container.getGitUrl() != null && !container.getGitUrl().isEmpty()) {
                automated = "Yes";
                gitUrl = container.getGitUrl();
            }

            if (container.getDescription() != null) {
                description = container.getDescription();
                if (description.length() > MAX_DESCRIPTION) {
                    description = description.substring(0, MAX_DESCRIPTION - PADDING) + "...";
                }
            }

            out(format, container.getToolPath(), description, gitUrl, boolWord(container.getIsRegistered()), cwl, automated);
        }
    }

    private static void printRegisteredList(List<Container> containers) {
        Collections.sort(containers, new ContainerComparator());

        int[] maxWidths = columnWidths(containers);

        int nameWidth = maxWidths[0] + PADDING;
        int descWidth = maxWidths[1] + PADDING;
        int gitWidth = maxWidths[2] + PADDING;
        String format = "%-" + nameWidth + "s%-" + descWidth + "s%-" + gitWidth + "s";
        out(format, NAME_HEADER, DESCRIPTION_HEADER, GIT_HEADER);

        for (Container container : containers) {
            String description = "";
            String gitUrl = "";

            if (container.getGitUrl() != null && !container.getGitUrl().isEmpty()) {
                gitUrl = container.getGitUrl();
            }

            if (container.getDescription() != null) {
                description = container.getDescription();
                if (description.length() > MAX_DESCRIPTION) {
                    description = description.substring(0, MAX_DESCRIPTION - PADDING) + "...";
                }
            }

            out(format, container.getToolPath(), description, gitUrl);
        }
    }

    private static void list(List<String> args) {
        try {
            // List<Container> containers = containersApi.allRegisteredContainers();
            List<Container> containers = usersApi.userRegisteredContainers(user.getId());
            printRegisteredList(containers);
        } catch (ApiException ex) {
            kill("Exception: " + ex);
        }
    }

    private static void search(List<String> args) {
        if (args.isEmpty()) {
            kill("Please provide a search term.");
        }
        String pattern = args.get(0);
        try {
            List<Container> containers = containersApi.search(pattern);

            out("MATCHING CONTAINERS");
            out("-------------------");
            printContainerList(containers);
        } catch (ApiException ex) {
            kill("Exception: " + ex);
        }
    }

    private static void publish(List<String> args) {
        if (args.isEmpty()) {
            try {
                List<Container> containers = usersApi.userContainers(user.getId());

                out("YOUR AVAILABLE CONTAINERS");
                out("-------------------");
                printContainerList(containers);
            } catch (ApiException ex) {
                out("Exception: " + ex);
            }
        } else {
            String first = args.get(0);
            if (isHelpRequest(first)) {
                publishHelp();
            } else {
                if (args.size() == 1) {
                    try {
                        Container container = containersApi.getContainerByToolPath(first);
                        RegisterRequest req = new RegisterRequest();
                        req.setRegister(true);
                        container = containersApi.register(container.getId(), req);

                        if (container != null) {
                            out("Successfully published " + first);
                        } else {
                            kill("Unable to publish invalid container " + first);
                        }
                    } catch (ApiException ex) {
                        kill("Unable to publish unknown container " + first);
                    }
                } else {
                    String toolname = args.get(1);
                    try {
                        Container container = containersApi.getContainerByToolPath(first);
                        Container newContainer = new Container();
                        // copy only the fields that we want to replicate, not sure why simply blanking
                        // the returned container does not work
                        newContainer.setMode(container.getMode());
                        newContainer.setName(container.getName());
                        newContainer.setNamespace(container.getNamespace());
                        newContainer.setRegistry(container.getRegistry());
                        newContainer.setDefaultDockerfilePath(container.getDefaultDockerfilePath());
                        newContainer.setDefaultCwlPath(container.getDefaultCwlPath());
                        newContainer.setIsPublic(container.getIsPublic());
                        newContainer.setIsRegistered(container.getIsRegistered());
                        newContainer.setGitUrl(container.getGitUrl());
                        newContainer.setPath(container.getPath());
                        newContainer.setToolname(toolname);

                        newContainer = containersApi.registerManual(newContainer);

                        if (newContainer != null) {
                            out("Successfully published " + toolname);
                        } else {
                            kill("Unable to publish " + toolname);
                        }
                    } catch (ApiException ex) {
                        kill("Unable to publish " + toolname);
                    }
                }
            }
        }
    }

    private static void manualPublish(List<String> args) {
        if (isHelp(args, true)) {
            out("");
            out("Usage: dockstore manual_publish --help");
            out("       dockstore manual_publish <params>");
            out("");
            out("Description:");
            out("  Manually register an entry in the dockstore. Currently this is used to " + "register entries for images on Docker Hub .");
            out("");
            out("Required parameters:");
            out("  --name <name>                Name for the docker container");
            out("  --namespace <namespace>      Organization for the docker container");
            out("  --git-url <url>              Reference to the git repo holding CWL and Dockerfile ex: \"<EMAIL>:user/test1.git\"");
            out("  --git-reference <reference>  Reference to git branch or tag where the CWL and Dockerfile is checked-in");
            out("Optional parameters:");
            out("  --dockerfile-path <file>     Path for the dockerfile, defaults to /Dockerfile/");
            out("  --cwl-path <file>            Path for the CWL document, defaults to /Dockstore.cwl");
            out("  --toolname <toolname>        Name of the tool, can be omitted");
            out("  --registry <registry>        Docker registry, can be omitted, defaults to registry.hub.docker.com");
            out("");
        } else {
            final String name = reqVal(args, "--name");
            final String namespace = reqVal(args, "--namespace");
            final String gitURL = reqVal(args, "--git-url");

            final String dockerfilePath = optVal(args, "--dockerfile-path", "/Dockerfile");
            final String cwlPath = optVal(args, "--cwl-path", "/Dockstore.cwl");
            final String gitReference = reqVal(args, "--git-reference");
            final String toolname = optVal(args, "--toolname", null);
            final String registry = optVal(args, "--registry", "registry.hub.docker.com");

            Container container = new Container();
            container.setMode(ModeEnum.MANUAL_IMAGE_PATH);
            container.setName(name);
            container.setNamespace(namespace);
            container.setRegistry("quay.io".equals(registry) ? RegistryEnum.QUAY_IO : RegistryEnum.DOCKER_HUB);
            container.setDefaultDockerfilePath(dockerfilePath);
            container.setDefaultCwlPath(cwlPath);
            container.setIsPublic(true);
            container.setIsRegistered(true);
            container.setGitUrl(gitURL);
            container.setToolname(toolname);
            Tag tag = new Tag();
            tag.setReference(gitReference);
            tag.setDockerfilePath(dockerfilePath);
            tag.setCwlPath(cwlPath);
            container.getTags().add(tag);
            String fullName = Joiner.on("/").skipNulls().join(registry, namespace, name, toolname);
            try {
                container = containersApi.registerManual(container);
                if (container != null) {
                    out("Successfully published " + fullName);
                } else {
                    kill("Unable to publish " + fullName);
                }
            } catch (final ApiException ex) {
                kill("Unable to publish " + fullName);
            }
        }
    }

    private static void dev(final List<String> args) throws ApiException {
        if (isHelp(args, true)) {
            out("");
            out("Usage: dockstore dev --help");
            out("       dockstore dev cwl2json");
            out("       dockstore dev tool2json <container>");
            out("");
            out("Description:");
            out("  Experimental features not quite ready for prime-time.");
            out("");
        } else {
            String cmd = args.remove(0);
            if (null != cmd) {
                switch (cmd) {
                case "cwl2json":
                    cwl2json(args);
                    break;
                case "tool2json":
                    tool2json(args);
                    break;
                default:
                    invalid(cmd);
                    break;
                }
            }
        }
    }

    private static void tool2json(final List<String> args) throws ApiException {
        if (isHelp(args, true)) {
            out("");
            out("Usage: dockstore dev --help");
            out("       dockstore dev tool2json");
            out("");
            out("Description:");
            out("  Spit out a json run file for a given cwl document.");
            out("Required parameters:");
            out("  --cwl <file>                Path to cwl file");
            out("");
        } else {
            final SourceFile cwlFromServer = getCWLFromServer(args);
            final Map<String, Object> runJson = cwl.extractRunJson(cwlFromServer.getContent());
            final Gson gson = cwl.getTypeSafeCWLToolDocument();
            out(gson.toJson(runJson));
        }
    }

    private static void cwl2json(final List<String> args) {
        if (isHelp(args, true)) {
            out("");
            out("Usage: dockstore dev --help");
            out("       dockstore dev cwl2json");
            out("");
            out("Description:");
            out("  Spit out a json run file for a given cwl document.");
            out("Required parameters:");
            out("  --cwl <file>                Path to cwl file");
            out("");
        } else {

            final String cwlPath = reqVal(args, "--cwl");
            final ImmutablePair<String, String> output = cwl.parseCWL(cwlPath, true);

            final Gson gson = CWL.getTypeSafeCWLToolDocument();
            final Map<String, Object> runJson = cwl.extractRunJson(output.getLeft());
            out(gson.toJson(runJson));
        }
    }


    /** this ends the section from dockstore-descriptor launcher **/

    private static boolean isHelpRequest(String first) {
        return "-h".equals(first) || "--help".equals(first);
    }

    private static void publishHelp() {
        out("");
        out("HELP FOR DOCKSTORE");
        out("------------------");
        out("See https://www.dockstore.org for more information");
        out("");
        out("dockstore publish                          :  lists the current and potential containers to share");
        out("");
        out("dockstore publish <container>              :  registers that container for use by others in the dockstore");
        out("");
        out("dockstore publish <container> <toolname>   :  registers that container for use by others in the dockstore under a specific toolname");
    }

    private static void info(List<String> args) {
        if (args.isEmpty()) {
            kill("Please provide a container.");
        }

        String path = args.get(0);
        try {
            Container container = containersApi.getContainerByToolPath(path);
            if (container == null || !container.getIsRegistered()) {
                kill("This container is not registered.");
            } else {
                // out(container.toString());
                // out(containersApi.getRegisteredContainer(path).getTags().toString());
                // Container container = containersApi.getRegisteredContainer(path);

                Date dateUploaded = container.getLastBuild();

                String description = container.getDescription();
                if (description == null) {
                    description = "";
                }

                String author = container.getAuthor();
                if (author == null) {
                    author = "";
                }

                String date = "";
                if (dateUploaded != null) {
                    date = dateUploaded.toString();
                }

                out("");
                out("DESCRIPTION:");
                out(description);
                out("AUTHOR:");
                out(author);
                out("DATE UPLOADED:");
                out(date);
                out("TAGS");

                List<Tag> tags = container.getTags();
                int tagSize = tags.size();
                StringBuilder builder = new StringBuilder();
                if (tagSize > 0) {
                    builder.append(tags.get(0).getName());
                    for (int i = 1; i < tagSize; i++) {
                        builder.append(", ").append(tags.get(i).getName());
                    }
                }

                out(builder.toString());

                out("GIT REPO:");
                out(container.getGitUrl());
                out("QUAY.IO REPO:");
                out("http://quay.io/repository/" + container.getNamespace() + "/" + container.getName());
                // out(container.toString());
            }
        } catch (ApiException ex) {
            // if (ex.getCode() == BAD_REQUEST) {
            // out("This container is not registered.");
            // } else {
            // out("Exception: " + ex);
            // }
            kill("Could not find container");
        }
    }

    private static void cwl(List<String> args) {
        if (args.isEmpty()) {
            kill("Please provide a container.");
        }

        try {
            SourceFile file = getCWLFromServer(args);

            if (file.getContent() != null && !file.getContent().isEmpty()) {
                out(file.getContent());
            } else {
                kill("No cwl file found.");
            }

        } catch (ApiException ex) {
            // out("Exception: " + ex);
            kill("Could not find container");
        }
    }

    private static SourceFile getCWLFromServer(List<String> args) throws ApiException {
        String[] parts = args.get(0).split(":");

        String path = parts[0];

        String tag = (parts.length > 1) ? parts[1] : null;
        SourceFile file = new SourceFile();
        Container container = containersApi.getContainerByToolPath(path);
        if (container.getValidTrigger()) {
            try {
                file = containersApi.cwl(container.getId(), tag);

            } catch (ApiException ex) {
                if (ex.getCode() == HttpStatus.SC_BAD_REQUEST) {
                    kill("Invalid tag");
                } else {
                    kill("No cwl file found.");
                }
            }
        } else {
            kill("No cwl file found.");
        }
        return file;
    }

    private static void refresh(List<String> args) {
        try {
            List<Container> containers = usersApi.refresh(user.getId());

            out("YOUR UPDATED CONTAINERS");
            out("-------------------");
            printContainerList(containers);
        } catch (ApiException ex) {
            kill("Exception: " + ex);
        }
    }

    public static void main(String[] argv) {
        List<String> args = new ArrayList<>(Arrays.asList(argv));

        if (flag(args, "--debug") || flag(args, "--d")) {
            DEBUG.set(true);
        }

        // user home dir
        String userHome = System.getProperty("user.home");

        try {
            String configFile = optVal(args, "--config", userHome + File.separator + ".dockstore" + File.separator + "config");
            InputStreamReader f = new InputStreamReader(new FileInputStream(configFile), Charset.defaultCharset());
            YamlReader reader = new YamlReader(f);
            Object object = reader.read();
            Map map = (Map) object;

            // pull out the variables from the config
            String token = (String) map.get("token");
            String serverUrl = (String) map.get("server-url");

            if (token == null) {
                err("The token is missing from your config file.");
                System.exit(GENERIC_ERROR);
            }
            if (serverUrl == null) {
                err("The server-url is missing from your config file.");
                System.exit(GENERIC_ERROR);
            }

            ApiClient defaultApiClient;
            defaultApiClient = Configuration.getDefaultApiClient();
            defaultApiClient.addDefaultHeader("Authorization", "Bearer " + token);
            defaultApiClient.setBasePath(serverUrl);
            containersApi = new ContainersApi(defaultApiClient);
            usersApi = new UsersApi(defaultApiClient);

            defaultApiClient.setDebugging(DEBUG.get());

            if (isHelp(args, true)) {
                out("");
                out("HELP FOR DOCKSTORE");
                out("------------------");
                out("See https://www.dockstore.org for more information");
                out("");
                out("Possible sub-commands include:");
                out("");
                out("  list             :  lists all the containers registered by the user ");
                out("");
                out("  search <pattern> :  allows a user to search for all containers that match the criteria");
                out("");
                out("  publish          :  register a container in the dockstore");
                out("");
                out("  manual_publish   :  register a Docker Hub container in the dockstore");
                out("");
                out("  info <container> :  print detailed information about a particular container");
                out("");
                out("  cwl <container>  :  returns the Common Workflow Language tool definition for this Docker image ");
                out("                      which enables integration with Global Alliance compliant systems");
                out("");
                out("  refresh          :  updates your list of containers stored on Dockstore");
                out("------------------");
                out("");
                out("Flags:");
                out("  --debug              Print debugging information");
                out("  --version            Print dockstore's version");
                out("  --config <file>      Override config file");
            } else {
                try {
                    // check user info after usage so that users can get usage without live webservice
                    user = usersApi.getUser();
                    if (user == null) {
                        throw new NotFoundException("User not found");
                    }

                    String cmd = args.remove(0);
                    if (null != cmd) {
                        switch (cmd) {
                        case "-v":
                        case "--version":
                            kill("dockstore: version information is provided by the wrapper script.");
                            break;
                        case "list":
                            list(args);
                            break;
                        case "search":
                            search(args);
                            break;
                        case "publish":
                            publish(args);
                            break;
                        case "manual_publish":
                            manualPublish(args);
                            break;
                        case "info":
                            info(args);
                            break;
                        case "cwl":
                            cwl(args);
                            break;
                        case "refresh":
                            refresh(args);
                            break;
                        case "dev":
                            dev(args);
                            break;
                        default:
                            invalid(cmd);
                            break;
                        }
                    }
                } catch (Kill k) {
                    System.exit(GENERIC_ERROR);
                }
            }
        } catch (IOException | NotFoundException | ApiException ex) {
            out("Exception: " + ex);
            System.exit(GENERIC_ERROR);
        } catch (ProcessingException ex) {
            out("Could not connect to Dockstore web service: " + ex);
            System.exit(CONNECTION_ERROR);
        }
    }
}
<commit_msg>Fix formatting<commit_after>/*
 * Copyright (C) 2015 Collaboratory
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package io.dockstore.client.cli;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;

import javax.ws.rs.ProcessingException;

import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.http.HttpStatus;

import com.esotericsoftware.yamlbeans.YamlReader;
import com.google.common.base.Joiner;
import com.google.gson.Gson;

import io.dockstore.common.CWL;
import io.swagger.client.ApiClient;
import io.swagger.client.ApiException;
import io.swagger.client.Configuration;
import io.swagger.client.api.ContainersApi;
import io.swagger.client.api.UsersApi;
import io.swagger.client.model.Container;
import io.swagger.client.model.Container.ModeEnum;
import io.swagger.client.model.Container.RegistryEnum;
import io.swagger.client.model.RegisterRequest;
import io.swagger.client.model.SourceFile;
import io.swagger.client.model.Tag;
import io.swagger.client.model.User;
import javassist.NotFoundException;

/*
 * Main entrypoint for the dockstore CLI. 
 * @author xliu
 *
 */
public class Client {

    private static ContainersApi containersApi;
    private static UsersApi usersApi;
    private static User user;
    private static CWL cwl;

    private static final String NAME_HEADER = "NAME";
    private static final String DESCRIPTION_HEADER = "DESCRIPTION";
    private static final String GIT_HEADER = "Git Repo";

    private static final int PADDING = 3;
    private static final int MAX_DESCRIPTION = 50;

    public static final int GENERIC_ERROR = 1;
    public static final int CONNECTION_ERROR = 150;

    private static void out(String format, Object... args) {
        System.out.println(String.format(format, args));
    }

    private static void out(String arg) {
        System.out.println(arg);
    }

    private static void err(String format, Object... args) {
        System.err.println(String.format(format, args));
    }

    public static final AtomicBoolean DEBUG = new AtomicBoolean(false);

    private static boolean isHelp(List<String> args, boolean valOnEmpty) {
        if (args.isEmpty()) {
            return valOnEmpty;
        }

        String first = args.get(0);
        return isHelpRequest(first);
    }

    private static class Kill extends RuntimeException {
    }

    private static void kill(String format, Object... args) {
        err(format, args);
        throw new Kill();
    }

    private static void invalid(String cmd) {
        kill("dockstore: '%s' is not a dockstore command. See 'dockstore --help'.", cmd);
    }

    private static void invalid(String cmd, String sub) {
        kill("dockstore: '%s %s' is not a dockstore command. See 'dockstore %s --help'.", cmd, sub, cmd);
    }

    private static boolean flag(List<String> args, String flag) {
        boolean found = false;
        for (int i = 0; i < args.size(); i++) {
            if (flag.equals(args.get(i))) {
                if (found) {
                    kill("consonance: multiple instances of '%s'.", flag);
                } else {
                    found = true;
                    args.remove(i);
                }
            }
        }
        return found;
    }

    private static String boolWord(boolean bool) {
        return bool ? "Yes" : "No";
    }

    private static List<String> optVals(List<String> args, String key) {
        List<String> vals = new ArrayList<>();

        for (int i = 0; i < args.size(); /** do nothing */
        i = i) {
            String s = args.get(i);
            if (key.equals(s)) {
                args.remove(i);
                if (i < args.size()) {
                    String val = args.remove(i);
                    if (!val.startsWith("--")) {
                        String[] ss = val.split(",");
                        if (ss.length > 0) {
                            vals.addAll(Arrays.asList(ss));
                            continue;
                        }
                    }
                }
                kill("dockstore: missing required argument to '%s'.", key);
            } else {
                i++;
            }
        }

        return vals;
    }

    private static String optVal(List<String> args, String key, String defaultVal) {
        String val = defaultVal;

        List<String> vals = optVals(args, key);
        if (vals.size() == 1) {
            val = vals.get(0);
        } else if (vals.size() > 1) {
            kill("dockstore: multiple instances of '%s'.", key);
        }

        return val;
    }

    private static String reqVal(List<String> args, String key) {
        String val = optVal(args, key, null);

        if (val == null) {
            kill("dockstore: missing required flag '%s'.", key);
        }

        return val;
    }

    private static int[] columnWidths(List<Container> containers) {
        int[] maxWidths = { NAME_HEADER.length(), DESCRIPTION_HEADER.length(), GIT_HEADER.length() };

        for (Container container : containers) {
            final String toolPath = container.getToolPath();
            if (toolPath != null && toolPath.length() > maxWidths[0]) {
                maxWidths[0] = toolPath.length();
            }
            final String description = container.getDescription();
            if (description != null && description.length() > maxWidths[1]) {
                maxWidths[1] = description.length();
            }
            final String gitUrl = container.getGitUrl();
            if (gitUrl != null && gitUrl.length() > maxWidths[2]) {
                maxWidths[2] = gitUrl.length();
            }
        }

        maxWidths[1] = (maxWidths[1] > MAX_DESCRIPTION) ? MAX_DESCRIPTION : maxWidths[1];

        return maxWidths;
    }

    private static class ContainerComparator implements Comparator<Container> {
        @Override
        public int compare(Container c1, Container c2) {
            String path1 = c1.getPath();
            String path2 = c2.getPath();

            return path1.compareToIgnoreCase(path2);
        }
    }

    private static void printContainerList(List<Container> containers) {
        Collections.sort(containers, new ContainerComparator());

        int[] maxWidths = columnWidths(containers);

        int nameWidth = maxWidths[0] + PADDING;
        int descWidth = maxWidths[1] + PADDING;
        int gitWidth = maxWidths[2] + PADDING;
        String format = "%-" + nameWidth + "s%-" + descWidth + "s%-" + gitWidth + "s%-16s%-16s%-10s";
        out(format, NAME_HEADER, DESCRIPTION_HEADER, GIT_HEADER, "On Dockstore?", "Dockstore.cwl", "Automated");

        for (Container container : containers) {
            String cwl = "No";
            String automated = "No";
            String description = "";
            String gitUrl = "";

            if (container.getValidTrigger()) {
                cwl = "Yes";
            }

            if (container.getGitUrl() != null && !container.getGitUrl().isEmpty()) {
                automated = "Yes";
                gitUrl = container.getGitUrl();
            }

            if (container.getDescription() != null) {
                description = container.getDescription();
                if (description.length() > MAX_DESCRIPTION) {
                    description = description.substring(0, MAX_DESCRIPTION - PADDING) + "...";
                }
            }

            out(format, container.getToolPath(), description, gitUrl, boolWord(container.getIsRegistered()), cwl, automated);
        }
    }

    private static void printRegisteredList(List<Container> containers) {
        Collections.sort(containers, new ContainerComparator());

        int[] maxWidths = columnWidths(containers);

        int nameWidth = maxWidths[0] + PADDING;
        int descWidth = maxWidths[1] + PADDING;
        int gitWidth = maxWidths[2] + PADDING;
        String format = "%-" + nameWidth + "s%-" + descWidth + "s%-" + gitWidth + "s";
        out(format, NAME_HEADER, DESCRIPTION_HEADER, GIT_HEADER);

        for (Container container : containers) {
            String description = "";
            String gitUrl = "";

            if (container.getGitUrl() != null && !container.getGitUrl().isEmpty()) {
                gitUrl = container.getGitUrl();
            }

            if (container.getDescription() != null) {
                description = container.getDescription();
                if (description.length() > MAX_DESCRIPTION) {
                    description = description.substring(0, MAX_DESCRIPTION - PADDING) + "...";
                }
            }

            out(format, container.getToolPath(), description, gitUrl);
        }
    }

    private static void list(List<String> args) {
        try {
            // List<Container> containers = containersApi.allRegisteredContainers();
            List<Container> containers = usersApi.userRegisteredContainers(user.getId());
            printRegisteredList(containers);
        } catch (ApiException ex) {
            kill("Exception: " + ex);
        }
    }

    private static void search(List<String> args) {
        if (args.isEmpty()) {
            kill("Please provide a search term.");
        }
        String pattern = args.get(0);
        try {
            List<Container> containers = containersApi.search(pattern);

            out("MATCHING CONTAINERS");
            out("-------------------");
            printContainerList(containers);
        } catch (ApiException ex) {
            kill("Exception: " + ex);
        }
    }

    private static void publish(List<String> args) {
        if (args.isEmpty()) {
            try {
                List<Container> containers = usersApi.userContainers(user.getId());

                out("YOUR AVAILABLE CONTAINERS");
                out("-------------------");
                printContainerList(containers);
            } catch (ApiException ex) {
                out("Exception: " + ex);
            }
        } else {
            String first = args.get(0);
            if (isHelpRequest(first)) {
                publishHelp();
            } else {
                if (args.size() == 1) {
                    try {
                        Container container = containersApi.getContainerByToolPath(first);
                        RegisterRequest req = new RegisterRequest();
                        req.setRegister(true);
                        container = containersApi.register(container.getId(), req);

                        if (container != null) {
                            out("Successfully published " + first);
                        } else {
                            kill("Unable to publish invalid container " + first);
                        }
                    } catch (ApiException ex) {
                        kill("Unable to publish unknown container " + first);
                    }
                } else {
                    String toolname = args.get(1);
                    try {
                        Container container = containersApi.getContainerByToolPath(first);
                        Container newContainer = new Container();
                        // copy only the fields that we want to replicate, not sure why simply blanking
                        // the returned container does not work
                        newContainer.setMode(container.getMode());
                        newContainer.setName(container.getName());
                        newContainer.setNamespace(container.getNamespace());
                        newContainer.setRegistry(container.getRegistry());
                        newContainer.setDefaultDockerfilePath(container.getDefaultDockerfilePath());
                        newContainer.setDefaultCwlPath(container.getDefaultCwlPath());
                        newContainer.setIsPublic(container.getIsPublic());
                        newContainer.setIsRegistered(container.getIsRegistered());
                        newContainer.setGitUrl(container.getGitUrl());
                        newContainer.setPath(container.getPath());
                        newContainer.setToolname(toolname);

                        newContainer = containersApi.registerManual(newContainer);

                        if (newContainer != null) {
                            out("Successfully published " + toolname);
                        } else {
                            kill("Unable to publish " + toolname);
                        }
                    } catch (ApiException ex) {
                        kill("Unable to publish " + toolname);
                    }
                }
            }
        }
    }

    private static void manualPublish(List<String> args) {
        if (isHelp(args, true)) {
            out("");
            out("Usage: dockstore manual_publish --help");
            out("       dockstore manual_publish <params>");
            out("");
            out("Description:");
            out("  Manually register an entry in the dockstore. Currently this is used to " + "register entries for images on Docker Hub .");
            out("");
            out("Required parameters:");
            out("  --name <name>                Name for the docker container");
            out("  --namespace <namespace>      Organization for the docker container");
            out("  --git-url <url>              Reference to the git repo holding CWL and Dockerfile ex: \"git@github.com:user/test1.git\"");
            out("  --git-reference <reference>  Reference to git branch or tag where the CWL and Dockerfile is checked-in");
            out("Optional parameters:");
            out("  --dockerfile-path <file>     Path for the dockerfile, defaults to /Dockerfile/");
            out("  --cwl-path <file>            Path for the CWL document, defaults to /Dockstore.cwl");
            out("  --toolname <toolname>        Name of the tool, can be omitted");
            out("  --registry <registry>        Docker registry, can be omitted, defaults to registry.hub.docker.com");
            out("");
        } else {
            final String name = reqVal(args, "--name");
            final String namespace = reqVal(args, "--namespace");
            final String gitURL = reqVal(args, "--git-url");

            final String dockerfilePath = optVal(args, "--dockerfile-path", "/Dockerfile");
            final String cwlPath = optVal(args, "--cwl-path", "/Dockstore.cwl");
            final String gitReference = reqVal(args, "--git-reference");
            final String toolname = optVal(args, "--toolname", null);
            final String registry = optVal(args, "--registry", "registry.hub.docker.com");

            Container container = new Container();
            container.setMode(ModeEnum.MANUAL_IMAGE_PATH);
            container.setName(name);
            container.setNamespace(namespace);
            container.setRegistry("quay.io".equals(registry) ? RegistryEnum.QUAY_IO : RegistryEnum.DOCKER_HUB);
            container.setDefaultDockerfilePath(dockerfilePath);
            container.setDefaultCwlPath(cwlPath);
            container.setIsPublic(true);
            container.setIsRegistered(true);
            container.setGitUrl(gitURL);
            container.setToolname(toolname);
            Tag tag = new Tag();
            tag.setReference(gitReference);
            tag.setDockerfilePath(dockerfilePath);
            tag.setCwlPath(cwlPath);
            container.getTags().add(tag);
            String fullName = Joiner.on("/").skipNulls().join(registry, namespace, name, toolname);
            try {
                container = containersApi.registerManual(container);
                if (container != null) {
                    out("Successfully published " + fullName);
                } else {
                    kill("Unable to publish " + fullName);
                }
            } catch (final ApiException ex) {
                kill("Unable to publish " + fullName);
            }
        }
    }

    private static void dev(final List<String> args) throws ApiException {
        if (isHelp(args, true)) {
            out("");
            out("Usage: dockstore dev --help");
            out("       dockstore dev cwl2json");
            out("       dockstore dev tool2json <container>");
            out("");
            out("Description:");
            out("  Experimental features not quite ready for prime-time.");
            out("");
        } else {
            String cmd = args.remove(0);
            if (null != cmd) {
                switch (cmd) {
                case "cwl2json":
                    cwl2json(args);
                    break;
                case "tool2json":
                    tool2json(args);
                    break;
                default:
                    invalid(cmd);
                    break;
                }
            }
        }
    }

    private static void tool2json(final List<String> args) throws ApiException {
        if (isHelp(args, true)) {
            out("");
            out("Usage: dockstore dev --help");
            out("       dockstore dev tool2json");
            out("");
            out("Description:");
            out("  Spit out a json run file for a given cwl document.");
            out("Required parameters:");
            out("  --cwl <file>                Path to cwl file");
            out("");
        } else {
            final SourceFile cwlFromServer = getCWLFromServer(args);
            final Map<String, Object> runJson = cwl.extractRunJson(cwlFromServer.getContent());
            final Gson gson = cwl.getTypeSafeCWLToolDocument();
            out(gson.toJson(runJson));
        }
    }

    private static void cwl2json(final List<String> args) {
        if (isHelp(args, true)) {
            out("");
            out("Usage: dockstore dev --help");
            out("       dockstore dev cwl2json");
            out("");
            out("Description:");
            out("  Spit out a json run file for a given cwl document.");
            out("Required parameters:");
            out("  --cwl <file>                Path to cwl file");
            out("");
        } else {

            final String cwlPath = reqVal(args, "--cwl");
            final ImmutablePair<String, String> output = cwl.parseCWL(cwlPath, true);

            final Gson gson = CWL.getTypeSafeCWLToolDocument();
            final Map<String, Object> runJson = cwl.extractRunJson(output.getLeft());
            out(gson.toJson(runJson));
        }
    }


    /** this ends the section from dockstore-descriptor launcher **/

    private static boolean isHelpRequest(String first) {
        return "-h".equals(first) || "--help".equals(first);
    }

    private static void publishHelp() {
        out("");
        out("HELP FOR DOCKSTORE");
        out("------------------");
        out("See https://www.dockstore.org for more information");
        out("");
        out("dockstore publish                          :  lists the current and potential containers to share");
        out("");
        out("dockstore publish <container>              :  registers that container for use by others in the dockstore");
        out("");
        out("dockstore publish <container> <toolname>   :  registers that container for use by others in the dockstore under a specific toolname");
    }

    private static void info(List<String> args) {
        if (args.isEmpty()) {
            kill("Please provide a container.");
        }

        String path = args.get(0);
        try {
            Container container = containersApi.getContainerByToolPath(path);
            if (container == null || !container.getIsRegistered()) {
                kill("This container is not registered.");
            } else {
                // out(container.toString());
                // out(containersApi.getRegisteredContainer(path).getTags().toString());
                // Container container = containersApi.getRegisteredContainer(path);

                Date dateUploaded = container.getLastBuild();

                String description = container.getDescription();
                if (description == null) {
                    description = "";
                }

                String author = container.getAuthor();
                if (author == null) {
                    author = "";
                }

                String date = "";
                if (dateUploaded != null) {
                    date = dateUploaded.toString();
                }

                out("");
                out("DESCRIPTION:");
                out(description);
                out("AUTHOR:");
                out(author);
                out("DATE UPLOADED:");
                out(date);
                out("TAGS");

                List<Tag> tags = container.getTags();
                int tagSize = tags.size();
                StringBuilder builder = new StringBuilder();
                if (tagSize > 0) {
                    builder.append(tags.get(0).getName());
                    for (int i = 1; i < tagSize; i++) {
                        builder.append(", ").append(tags.get(i).getName());
                    }
                }

                out(builder.toString());

                out("GIT REPO:");
                out(container.getGitUrl());
                out("QUAY.IO REPO:");
                out("http://quay.io/repository/" + container.getNamespace() + "/" + container.getName());
                // out(container.toString());
            }
        } catch (ApiException ex) {
            // if (ex.getCode() == BAD_REQUEST) {
            // out("This container is not registered.");
            // } else {
            // out("Exception: " + ex);
            // }
            kill("Could not find container");
        }
    }

    private static void cwl(List<String> args) {
        if (args.isEmpty()) {
            kill("Please provide a container.");
        }

        try {
            SourceFile file = getCWLFromServer(args);

            if (file.getContent() != null && !file.getContent().isEmpty()) {
                out(file.getContent());
            } else {
                kill("No cwl file found.");
            }

        } catch (ApiException ex) {
            // out("Exception: " + ex);
            kill("Could not find container");
        }
    }

    private static SourceFile getCWLFromServer(List<String> args) throws ApiException {
        String[] parts = args.get(0).split(":");

        String path = parts[0];

        String tag = (parts.length > 1) ? parts[1] : null;
        SourceFile file = new SourceFile();
        Container container = containersApi.getContainerByToolPath(path);
        if (container.getValidTrigger()) {
            try {
                file = containersApi.cwl(container.getId(), tag);

            } catch (ApiException ex) {
                if (ex.getCode() == HttpStatus.SC_BAD_REQUEST) {
                    kill("Invalid tag");
                } else {
                    kill("No cwl file found.");
                }
            }
        } else {
            kill("No cwl file found.");
        }
        return file;
    }

    private static void refresh(List<String> args) {
        try {
            List<Container> containers = usersApi.refresh(user.getId());

            out("YOUR UPDATED CONTAINERS");
            out("-------------------");
            printContainerList(containers);
        } catch (ApiException ex) {
            kill("Exception: " + ex);
        }
    }

    public static void main(String[] argv) {
        List<String> args = new ArrayList<>(Arrays.asList(argv));

        if (flag(args, "--debug") || flag(args, "--d")) {
            DEBUG.set(true);
        }

        // user home dir
        String userHome = System.getProperty("user.home");

        try {
            String configFile = optVal(args, "--config", userHome + File.separator + ".dockstore" + File.separator + "config");
            InputStreamReader f = new InputStreamReader(new FileInputStream(configFile), Charset.defaultCharset());
            YamlReader reader = new YamlReader(f);
            Object object = reader.read();
            Map map = (Map) object;

            // pull out the variables from the config
            String token = (String) map.get("token");
            String serverUrl = (String) map.get("server-url");

            if (token == null) {
                err("The token is missing from your config file.");
                System.exit(GENERIC_ERROR);
            }
            if (serverUrl == null) {
                err("The server-url is missing from your config file.");
                System.exit(GENERIC_ERROR);
            }

            ApiClient defaultApiClient;
            defaultApiClient = Configuration.getDefaultApiClient();
            defaultApiClient.addDefaultHeader("Authorization", "Bearer " + token);
            defaultApiClient.setBasePath(serverUrl);
            containersApi = new ContainersApi(defaultApiClient);
            usersApi = new UsersApi(defaultApiClient);

            defaultApiClient.setDebugging(DEBUG.get());

            if (isHelp(args, true)) {
                out("");
                out("HELP FOR DOCKSTORE");
                out("------------------");
                out("See https://www.dockstore.org for more information");
                out("");
                out("Possible sub-commands include:");
                out("");
                out("  list             :  lists all the containers registered by the user ");
                out("");
                out("  search <pattern> :  allows a user to search for all containers that match the criteria");
                out("");
                out("  publish          :  register a container in the dockstore");
                out("");
                out("  manual_publish   :  register a Docker Hub container in the dockstore");
                out("");
                out("  info <container> :  print detailed information about a particular container");
                out("");
                out("  cwl <container>  :  returns the Common Workflow Language tool definition for this Docker image ");
                out("                      which enables integration with Global Alliance compliant systems");
                out("");
                out("  refresh          :  updates your list of containers stored on Dockstore");
                out("------------------");
                out("");
                out("Flags:");
                out("  --debug              Print debugging information");
                out("  --version            Print dockstore's version");
                out("  --config <file>      Override config file");
            } else {
                try {
                    // check user info after usage so that users can get usage without live webservice
                    user = usersApi.getUser();
                    if (user == null) {
                        throw new NotFoundException("User not found");
                    }

                    String cmd = args.remove(0);
                    if (null != cmd) {
                        switch (cmd) {
                        case "-v":
                        case "--version":
                            kill("dockstore: version information is provided by the wrapper script.");
                            break;
                        case "list":
                            list(args);
                            break;
                        case "search":
                            search(args);
                            break;
                        case "publish":
                            publish(args);
                            break;
                        case "manual_publish":
                            manualPublish(args);
                            break;
                        case "info":
                            info(args);
                            break;
                        case "cwl":
                            cwl(args);
                            break;
                        case "refresh":
                            refresh(args);
                            break;
                        case "dev":
                            dev(args);
                            break;
                        default:
                            invalid(cmd);
                            break;
                        }
                    }
                } catch (Kill k) {
                    System.exit(GENERIC_ERROR);
                }
            }
        } catch (IOException | NotFoundException | ApiException ex) {
            out("Exception: " + ex);
            System.exit(GENERIC_ERROR);
        } catch (ProcessingException ex) {
            out("Could not connect to Dockstore web service: " + ex);
            System.exit(CONNECTION_ERROR);
        }
    }
}

{'fraction_non_alphanumeric': 0.11439448571955657, 'fraction_numerical': 0.0025491807402686704, 'mean_word_length': 1.566190394215872, 'pattern_counts': {'":': 26, '<': 122, '<?xml version=': 0, '>': 134, 'https://': 4, 'lorem ipsum': 0, 'www.': 6, 'xml': 0}, 'pii_count': 1, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2496923', 'n_tokens_mistral': 15078, 'n_tokens_neox': 14370, 'n_words': 3870}