/*
 * RHQ Management Platform
 * Copyright (C) 2005-2008 Red Hat, Inc.
 * All rights reserved.
 *
 * 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 version 2 of the License.
 *
 * 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, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
package org.rhq.enterprise.client.proxy;

import org.rhq.enterprise.client.ClientMain;
import org.rhq.enterprise.client.TabularWriter;
import org.rhq.core.domain.configuration.Configuration;
import org.rhq.core.domain.configuration.PropertySimple;
import org.rhq.core.domain.configuration.definition.ConfigurationDefinition;
import org.rhq.core.domain.configuration.definition.ConfigurationTemplate;
import org.rhq.core.domain.configuration.definition.PropertyGroupDefinition;
import org.rhq.core.domain.configuration.definition.PropertyDefinition;
import org.rhq.core.domain.configuration.definition.PropertyDefinitionSimple;
import org.rhq.core.domain.configuration.definition.PropertyDefinitionEnumeration;
import org.rhq.core.domain.configuration.definition.PropertySimpleType;

import java.io.PrintWriter;
import java.io.IOException;
import java.util.Map;
import java.util.List;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;

import jline.ConsoleReader;
import jline.ConsoleOperations;

/**
 * @author Greg Hinkle
 */
public class ConfigurationEditor {

    private ClientMain client;
    private PrintWriter writer;
    private ConsoleReader console;


    public ConfigurationEditor(ClientMain client) {
        this.client = client;
        this.writer = client.getPrintWriter();
        this.console = client.getConsoleReader();
    }


    /**
     * Edit a configuration interactively. Return the altered config if the user chooses
     * to save or null otherwise.
     *
     * @param def
     * @param config
     * @return
     * @throws IOException
     */
    public Configuration editConfiguration(ConfigurationDefinition def, Configuration config) {

        try {
            Configuration newConfig = config.deepCopy();
            newConfig = editExistingConfiguration(def, newConfig);

            while (true) {
                String input = console.readLine("[R]eview, [E]dit, Re[V]ert [S]ave or [C]ancel: ");

                char inputChar = input.charAt(0);
                switch (inputChar) {
                    case 'r':
                    case 'R':
                        printConfiguration(newConfig);
                        continue;
                    case 'e':
                    case 'E':
                        editExistingConfiguration(def, newConfig);
                        continue;
                    case 'v':
                    case 'V':
                        newConfig = config;
                        continue;
                    case 's':
                    case 'S':
                        return newConfig;
                    case 'c':
                    case 'C':
                        return null;
                    default:
                        writer.println("unknown option");
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        } catch (QuitException e) {
            writer.println("Cancelling edit");
            return null;
        }
    }


    public Configuration createConfiguration(ConfigurationDefinition def) throws IOException, QuitException {
        if (client.isInteractiveMode()) {
            throw new UnsupportedOperationException("Configuration wizard is only available in interactive model.");
        }

        Map<String, ConfigurationTemplate> templates = def.getTemplates();

        ConfigurationTemplate template = (ConfigurationTemplate) question(templates, null, "template", "Select from the available templates");

        return editConfiguration(def, template == null ? null : template.createConfiguration());
    }

    public Configuration editExistingConfiguration(ConfigurationDefinition def, Configuration config) throws IOException, QuitException {
        List<PropertyGroupDefinition> groups = new ArrayList<PropertyGroupDefinition>(def.getGroupDefinitions());

        if (config == null) {
            config = new Configuration();
        }

        writer.println("Non-Grouped Properties: ");
        List<PropertyDefinition> properties = new ArrayList<PropertyDefinition>(def.getNonGroupedProperties());
        Collections.sort(properties, new Comparator<PropertyDefinition>() {
            public int compare(PropertyDefinition o1, PropertyDefinition o2) {
                return new Integer(o1.getOrder()).compareTo(new Integer(o2.getOrder()));
            }
        });

        for (PropertyDefinition propDef : properties) {
            question(propDef, config);
        }


        Collections.sort(groups, new Comparator<PropertyGroupDefinition>() {
            public int compare(PropertyGroupDefinition o1, PropertyGroupDefinition o2) {
                return new Integer(o1.getOrder()).compareTo(new Integer(o2.getOrder()));
            }
        });


        for (PropertyGroupDefinition groupDef : groups) {
            writer.println("Group: " + groupDef.getDisplayName());

            properties = new ArrayList<PropertyDefinition>(def.getPropertiesInGroup(groupDef.getName()));
            Collections.sort(properties, new Comparator<PropertyDefinition>() {
                public int compare(PropertyDefinition o1, PropertyDefinition o2) {
                    return new Integer(o1.getOrder()).compareTo(new Integer(o2.getOrder()));
                }
            });

            for (PropertyDefinition propDef : properties) {
                question(propDef, config);
            }
        }


        return config;
    }


    private void question(PropertyDefinition definition, Configuration template) throws IOException, QuitException {
        if (definition instanceof PropertyDefinitionSimple) {
            PropertyDefinitionSimple simpleDef = (PropertyDefinitionSimple) definition;
            String base = null;
            if (template != null && template.getSimple(definition.getName()) != null) {
                PropertySimple simple = template.getSimple(definition.getName());

                if (simple == null) {
                    simple = new PropertySimple(definition.getName(), null);
                    template.put(simple);
                }

                List<PropertyDefinitionEnumeration> optionList = simpleDef.getEnumeratedValues();
                if (optionList != null && optionList.size() > 0) {
                    // Select from a set of enumerate options

                    Map options = new LinkedHashMap();
                    for (PropertyDefinitionEnumeration enumValue : simpleDef.getEnumeratedValues()) {
                        options.put(enumValue.getName(), enumValue.getValue());
                    }

                    Object result = question(options, simple.getStringValue(), definition.getName(), definition.getDescription());
                    if (result == null) {
                        template.remove(simple.getName());
                    } else {
                        simple.setValue(result);
                    }
                } else {

                    while (true) {
                        String currentValue = simple.getStringValue();
                        String prompt = definition.getName() + (currentValue != null ? "[" + currentValue + "]" : "") + ": ";

                        String input = getInput(prompt, definition.getDescription());
                        if (input == null) {
                            template.remove(simple.getName());
                            break;
                        } else {

                            try {
                                String newval = validate(simpleDef, input);
                                simple.setStringValue(newval);
                                break;
                            } catch (Exception e) {
                                writer.println("Invalid value for " + simpleDef.getType().name() + ": " + e.getMessage());
                            }
                        }
                    }
                }
            }
        }
    }


    public String validate(PropertyDefinitionSimple def, String value) {

        switch (def.getType()) {
            case BOOLEAN:
                if ("y".equalsIgnoreCase(value) || "yes".equalsIgnoreCase(value) || "t".equalsIgnoreCase(value) || "true".equalsIgnoreCase(value)) {
                    return Boolean.TRUE.toString();
                } else if ("n".equalsIgnoreCase(value) || "no".equalsIgnoreCase(value) || "f".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value)) {
                    return Boolean.FALSE.toString();
                }
                break;
            case INTEGER:
                return String.valueOf(Integer.parseInt(value));
            case LONG:
                return String.valueOf(Long.parseLong(value));
            case FLOAT:
                return String.valueOf(Float.parseFloat(value));
            case DOUBLE:
                return String.valueOf(Double.parseDouble(value));
        }
        return null;
    }


    private Object question(Map map, String currentValue, String prompt, String help) throws IOException, QuitException {
        PrintWriter writer = client.getPrintWriter();

        if (map != null && map.size() > 0) {
            writer.println("Select a " + prompt + ":");

            int i = 1;
            int currentSelection = -1;
            for (Object key : map.keySet()) {
                if (currentValue != null && currentValue.equals(map.get(key)))
                    currentSelection = i;
                writer.println("\t" + i++ + ") " + key);

            }

            writer.flush();

            Object answer = null;
            while (answer == null) {
                String input = getInput(prompt + "[" + currentSelection + "]:", help);

                int index = -1;
                if (input == null) {
                    return null;
                } else if (input.length() == 0) {
                    index = currentSelection;
                } else {
                    index = Integer.parseInt(input);
                }

                if (index == 0) {
                    return null;
                }

                i = 1;
                for (Object templateName : map.keySet()) {
                    if (i++ == index) {
                        answer = map.get(templateName);
                        writer.println("\t" + templateName + " selected.");
                    }
                }
            }
            return answer;
        } else {
            return null;
        }
    }


    private void printConfiguration(Configuration configuration) {
        TabularWriter tw = new TabularWriter(writer);
        tw.print(configuration);
    }


    private String getInput(String prompt, String extraHelp) throws IOException, QuitException {
        StringBuilder buf = new StringBuilder();
        writer.print(prompt);
        writer.flush();
        try {
            while (true) {
                int ch = console.readVirtualKey();

                switch ((char) ch) {
                    case ConsoleOperations.CTRL_K:
                        // quit editing
                        throw new QuitException();
                    case ConsoleOperations.CTRL_D:
                        // unset
                        return null;
                    case ConsoleOperations.CTRL_E:
                        writer.println();
                        writer.println(extraHelp);
                        writer.print(prompt);
                        writer.print(buf.toString());
                        writer.print(prompt);
                        writer.flush();                        
                        break;
                    case 10:
                        return buf.toString();
                    default:
                        buf.append(ch);
                        break;

                }
            }
        } finally {
            writer.println();
            writer.flush();
        }
    }

    static class QuitException extends Exception {
    }

}
