package autotest.common;

import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.http.client.URL;
import com.google.gwt.i18n.client.NumberFormat;
import com.google.gwt.json.client.JSONArray;
import com.google.gwt.json.client.JSONNumber;
import com.google.gwt.json.client.JSONObject;
import com.google.gwt.json.client.JSONString;
import com.google.gwt.json.client.JSONValue;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.HTMLPanel;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Utils {
    public static final String JSON_NULL = "<null>";
    public static final String RETRIEVE_LOGS_URL = "/retrieve_logs/";

    private static final String[][] escapeMappings = {
        {"&", "&amp;"},
        {">", "&gt;"},
        {"<", "&lt;"},
        {"\"", "&quot;"},
        {"'", "&apos;"},
        {"#", "%23"},
    };

    /**
     * Converts a collection of Java <code>String</code>s into a <code>JSONArray
     * </code> of <code>JSONString</code>s.
     */
    public static JSONArray stringsToJSON(Collection<String> strings) {
        JSONArray result = new JSONArray();
        for(String s : strings) {
            result.set(result.size(), new JSONString(s));
        }
        return result;
    }

    /**
     * Converts a collection of Java <code>Integers</code>s into a <code>JSONArray
     * </code> of <code>JSONNumber</code>s.
     */
    public static JSONArray integersToJSON(Collection<Integer> integers) {
        JSONArray result = new JSONArray();
        for(Integer i : integers) {
            result.set(result.size(), new JSONNumber(i));
        }
        return result;
    }

    /**
     * Converts a <code>JSONArray</code> of <code>JSONStrings</code> to an
     * array of Java <code>Strings</code>.
     */
    public static String[] JSONtoStrings(JSONArray strings) {
        String[] result = new String[strings.size()];
        for (int i = 0; i < strings.size(); i++) {
            result[i] = jsonToString(strings.get(i));
        }
        return result;
    }

    /**
     * Converts a <code>JSONArray</code> of <code>JSONObjects</code> to an
     * array of Java <code>Strings</code> by grabbing the specified field from
     * each object.
     */
    public static String[] JSONObjectsToStrings(JSONArray objects, String field) {
        String[] result = new String[objects.size()];
        for (int i = 0; i < objects.size(); i++) {
            JSONValue fieldValue = objects.get(i).isObject().get(field);
            result[i] = jsonToString(fieldValue);
        }
        return result;
    }

    public static JSONObject mapToJsonObject(Map<String, String> map) {
        JSONObject result = new JSONObject();
        for (Map.Entry<String, String> entry : map.entrySet()) {
            result.put(entry.getKey(), new JSONString(entry.getValue()));
        }
        return result;
    }

    public static Map<String, String> jsonObjectToMap(JSONObject object) {
        Map<String, String> result = new HashMap<String, String>();
        for (String key : object.keySet()) {
            result.put(key, jsonToString(object.get(key)));
        }
        return result;
    }

    /**
     * Get a value out of a JSONObject list of size 1.
     * @return list[0]
     * @throws IllegalArgumentException if the list is not of size 1
     */
    public static JSONObject getSingleObjectFromList(List<JSONObject> list) {
        if(list.size() != 1) {
            throw new IllegalArgumentException("List is not of size 1");
        }
        return list.get(0);
    }

    public static JSONObject getSingleObjectFromArray(JSONArray array) {
        return getSingleObjectFromList(new JSONArrayList<JSONObject>(array));
    }

    public static JSONObject copyJSONObject(JSONObject source) {
        JSONObject dest = new JSONObject();
        for(String key : source.keySet()) {
            dest.put(key, source.get(key));
        }
        return dest;
    }

    public static String escape(String text) {
        for (String[] mapping : escapeMappings) {
            text = text.replace(mapping[0], mapping[1]);
        }
        return text;
    }

    public static String unescape(String text) {
        // must iterate in reverse order
        for (int i = escapeMappings.length - 1; i >= 0; i--) {
            text = text.replace(escapeMappings[i][1], escapeMappings[i][0]);
        }
        return text;
    }

    public static <T> List<T> wrapObjectWithList(T object) {
        List<T> list = new ArrayList<T>();
        list.add(object);
        return list;
    }

    public static <T> String joinStrings(String joiner, List<T> objects, boolean wantBlanks) {
        StringBuilder result = new StringBuilder();
        boolean first = true;
        for (T object : objects) {
            String piece = object.toString();
            if (piece.equals("") && !wantBlanks) {
                continue;
            }
            if (first) {
                first = false;
            } else {
                result.append(joiner);
            }
            result.append(piece);
        }
        return result.toString();
    }

    public static <T> String joinStrings(String joiner, List<T> objects) {
        return joinStrings(joiner, objects, false);
    }

    public static Map<String,String> decodeUrlArguments(String urlArguments,
                                                        Map<String, String> arguments) {
        String[] components = urlArguments.split("&");
        for (String component : components) {
            String[] parts = component.split("=");
            if (parts.length > 2) {
                throw new IllegalArgumentException();
            }
            String key = decodeComponent(parts[0]);
            String value = "";
            if (parts.length == 2) {
                value = URL.decodeComponent(parts[1]);
            }
            arguments.put(key, value);
        }
        return arguments;
    }

    private static String decodeComponent(String component) {
        return URL.decodeComponent(component.replace("%27", "'"));
    }

    public static String encodeUrlArguments(Map<String, String> arguments) {
        List<String> components = new ArrayList<String>();
        for (Map.Entry<String, String> entry : arguments.entrySet()) {
            String key = encodeComponent(entry.getKey());
            String value = encodeComponent(entry.getValue());
            components.add(key + "=" + value);
        }
        return joinStrings("&", components);
    }

    private static String encodeComponent(String component) {
        return URL.encodeComponent(component).replace("'", "%27");
    }

    /**
     * @param path should be of the form "123-showard/status.log" or just "123-showard"
     */
    public static String getLogsUrl(String path) {
        return "/results/" + path;
    }

    public static String getRetrieveLogsUrl(String path) {
        String logUrl = URL.encode(getLogsUrl(path));
        return RETRIEVE_LOGS_URL + "?job=" + logUrl;
    }

    public static String jsonToString(JSONValue value) {
        JSONString string;
        JSONNumber number;
        assert value != null;
        if ((string = value.isString()) != null) {
            return string.stringValue();
        }
        if ((number = value.isNumber()) != null) {
            double doubleValue = number.doubleValue();
            if (doubleValue == (int) doubleValue) {
                return Integer.toString((int) doubleValue);
            }
            return Double.toString(doubleValue);

        }
        if (value.isNull() != null) {
            return JSON_NULL;
        }
        return value.toString();
    }

    public static String setDefaultValue(Map<String, String> map, String key, String defaultValue) {
        if (map.containsKey(key)) {
            return map.get(key);
        }
        map.put(key, defaultValue);
        return defaultValue;
    }

    public static JSONValue setDefaultValue(JSONObject object, String key, JSONValue defaultValue) {
        if (object.containsKey(key)) {
            return object.get(key);
        }
        object.put(key, defaultValue);
        return defaultValue;
    }

    public static List<String> splitList(String list, String splitRegex) {
        String[] parts = list.split(splitRegex);
        List<String> finalParts = new ArrayList<String>();
        for (String part : parts) {
            if (!part.equals("")) {
                finalParts.add(part);
            }
        }
        return finalParts;
    }

    public static List<String> splitList(String list) {
        return splitList(list, ",");
    }

    public static List<String> splitListWithSpaces(String list) {
        return splitList(list, "[,\\s]+");
    }

    public static void updateObject(JSONObject destination, JSONObject source) {
        if (source == null) {
            return;
        }
        for (String key : source.keySet()) {
            destination.put(key, source.get(key));
        }
    }

    public static void openUrlInNewWindow(String url) {
        Window.open(url, "_blank", "");
    }

    public static HTMLPanel divToPanel(String elementId) {
        Element divElement = Document.get().getElementById(elementId);
        divElement.getParentElement().removeChild(divElement);
        return new HTMLPanel(divElement.getInnerHTML());
    }

    public static void setElementVisible(String elementId, boolean visible) {
        String display = visible ? "block" : "none";
        Document.get().getElementById(elementId).getStyle().setProperty("display", display);
    }

    public static String percentage(int num, int total) {
        if (total == 0) {
            return "N/A";
        }
        NumberFormat format = NumberFormat.getFormat("##0.#%");
        return format.format(num / (double) total);
    }

    public static String numberAndPercentage(int num, int total) {
        return num + " (" + percentage(num, total) + ")";
    }

    public static interface JsonObjectFactory<T> {
        public T fromJsonObject(JSONObject object);
    }

    public static <T> List<T> createList(JSONArray objects, JsonObjectFactory<T> factory) {
        List<T> list = new ArrayList<T>();
        for (JSONObject object : new JSONArrayList<JSONObject>(objects)) {
            list.add(factory.fromJsonObject(object));
        }
        return list;
    }

    public static String getBaseUrl() {
        return Window.Location.getProtocol() + "//" + Window.Location.getHost();
    }
    
    /**
     * Split string into multiple lines, preserving words, insert "\n" at the end of lines
     * @param input text to be split
     * @param maxCharInLine maximum number of characters allowed in a line
     * @return
     */
    public static String splitIntoLines(String input, int maxCharInLine){
        StringBuilder output = new StringBuilder(input.length());
        int lineLen = 0;
    	String[] words = input.split("\\s"); // split on all whitespace
    	int wordIndex = 0;
        while (wordIndex < words.length) {
            String word = words[wordIndex++];
            while(word.length() > maxCharInLine) { // break words longer than line limit
                output.append(word.substring(0, maxCharInLine-lineLen) + "\n");
                word = word.substring(maxCharInLine - lineLen);
                lineLen = 0;
            }
            if (lineLen + word.length() > maxCharInLine) { // break before word breaks line limit
                output.append("\n");
                lineLen = 0;
                if ( word.length() == 0 ) // skip line ending blanks
                	continue;
            }
            output.append(word + " ");
            lineLen += word.length() + 1;
        }
        return output.toString();
    }
    
}
