package com.hardbacknutter.org.json;

/*
Public Domain.
 */

/**
 * This provides static methods to convert comma (or otherwise) delimited text into a
 * JSONArray, and to convert a JSONArray into comma (or otherwise) delimited text. Comma
 * delimited text is a very popular format for data interchange. It is
 * understood by most database, spreadsheet, and organizer programs.
 * <p>
 * Each row of text represents a row in a table or a data record. Each row
 * ends with a NEWLINE character. Each row contains one or more values.
 * Values are separated by commas. A value can contain any character except
 * for comma, unless it is wrapped in single quotes or double quotes.
 * <p>
 * The first row usually contains the names of the columns.
 * <p>
 * A comma delimited list can be converted into a JSONArray of JSONObjects.
 * The names for the elements in the JSONObjects can be taken from the names
 * in the first row.
 * @author JSON.org
 * @version 2016-05-01
 */
@SuppressWarnings("ALL")
public class CDL {

    /**
     * Constructs a new CDL object.
     * @deprecated (Utility class cannot be instantiated)
     */
    @Deprecated
    public CDL() {
    }

    /**
     * Get the next value. The value can be wrapped in quotes. The value can
     * be empty.
     * @param x A JSONTokener of the source text.
     * @param delimiter used in the file
     * @return The value string, or null if empty.
     * @throws JSONException if the quoted string is badly formed.
     */
    private static String getValue(JSONTokener x, char delimiter) throws JSONException {
        char c;
        char q;
        StringBuilder sb;
        do {
            c = x.next();
        } while (c == ' ' || c == '\t');
        if (c == 0) {
            return null;
        } else if (c == '"' || c == '\'') {
            q = c;
            sb = new StringBuilder();
            for (;;) {
                c = x.next();
                if (c == q) {
                    //Handle escaped double-quote
                    char nextC = x.next();
                    if (nextC != '\"') {
                        // if our quote was the end of the file, don't step
                        if (nextC > 0) {
                            x.back();
                        }
                        break;
                    }
                }
                if (c == 0 || c == '\n' || c == '\r') {
                    throw x.syntaxError("Missing close quote '" + q + "'.");
                }
                sb.append(c);
            }
            return sb.toString();
        } else if (c == delimiter) {
            x.back();
            return "";
        }
        x.back();
        return x.nextTo(delimiter);
    }

    /**
     * Produce a JSONArray of strings from a row of comma delimited values.
     * @param x A JSONTokener of the source text.
     * @return A JSONArray of strings.
     * @throws JSONException if a called function fails
     */
    public static JSONArray rowToJSONArray(JSONTokener x) throws JSONException {
        return rowToJSONArray(x, ',');
    }

    /**
     * Produce a JSONArray of strings from a row of comma delimited values.
     * @param x A JSONTokener of the source text.
     * @param delimiter custom delimiter char
     * @return A JSONArray of strings.
     * @throws JSONException if a called function fails
     */
    public static JSONArray rowToJSONArray(JSONTokener x, char delimiter) throws JSONException {
        JSONArray ja = new JSONArray();
        for (;;) {
            String value = getValue(x,delimiter);
            char c = x.next();
            if (value != null) {
                ja.put(value);
            } else if (ja.length() == 0 && c != delimiter) {
                return null;
            } else {
                // This line accounts for CSV ending with no newline
                ja.put("");
            }

            for (;;) {
                if (c == delimiter) {
                    break;
                }
                if (c != ' ') {
                    if (c == '\n' || c == '\r' || c == 0) {
                        return ja;
                    }
                    throw x.syntaxError("Bad character '" + c + "' (" +
                            (int)c + ").");
                }
                c = x.next();
            }
        }
    }

    /**
     * Produce a JSONObject from a row of comma delimited text, using a
     * parallel JSONArray of strings to provides the names of the elements.
     * @param names A JSONArray of names. This is commonly obtained from the
     *  first row of a comma delimited text file using the rowToJSONArray
     *  method.
     * @param x A JSONTokener of the source text.
     * @return A JSONObject combining the names and values.
     * @throws JSONException if a called function fails
     */
    public static JSONObject rowToJSONObject(JSONArray names, JSONTokener x) throws JSONException {
        return rowToJSONObject(names, x, ',');
    }

    /**
     * Produce a JSONObject from a row of comma delimited text, using a
     * parallel JSONArray of strings to provides the names of the elements.
     * @param names A JSONArray of names. This is commonly obtained from the
     *  first row of a comma delimited text file using the rowToJSONArray
     *  method.
     * @param x A JSONTokener of the source text.
     * @param delimiter custom delimiter char
     * @return A JSONObject combining the names and values.
     * @throws JSONException if a called function fails
     */
    public static JSONObject rowToJSONObject(JSONArray names, JSONTokener x, char delimiter) throws JSONException {
        JSONArray ja = rowToJSONArray(x, delimiter);
        return ja != null ? ja.toJSONObject(names) :  null;
    }

    /**
     * Produce a comma delimited text row from a JSONArray. Values containing
     * the comma character will be quoted. Troublesome characters may be
     * removed.
     * @param ja A JSONArray of strings.
     * @return A string ending in NEWLINE.
     */
    public static String rowToString(JSONArray ja) {
        return rowToString(ja, ',');
    }

    /**
     * Produce a comma delimited text row from a JSONArray. Values containing
     * the comma character will be quoted. Troublesome characters may be
     * removed.
     * @param ja A JSONArray of strings.
     * @param delimiter custom delimiter char
     * @return A string ending in NEWLINE.
     */
    public static String rowToString(JSONArray ja, char delimiter) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < ja.length(); i += 1) {
            if (i > 0) {
                sb.append(delimiter);
            }
            Object object = ja.opt(i);
            appendRowValue(sb, object, delimiter);
        }
        sb.append('\n');
        return sb.toString();
    }

    /**
     * Append a single row value, quoting it when required by the delimiter or
     * content.
     *
     * @param sb the destination buffer
     * @param object the value to append
     * @param delimiter the delimiter used between row values
     */
    private static void appendRowValue(StringBuilder sb, Object object, char delimiter) {
        if (object == null) {
            return;
        }
        String string = object.toString();
        if (shouldQuoteValue(string, delimiter)) {
            appendQuotedValue(sb, string);
        } else {
            sb.append(string);
        }
    }

    /**
     * Determine whether a row value should be quoted.
     *
     * @param value the row value to evaluate
     * @param delimiter the delimiter used between row values
     * @return {@code true} if the value should be quoted
     */
    private static boolean shouldQuoteValue(String value, char delimiter) {
        if (value.isEmpty()) {
            return false;
        }
        boolean containsDelimiter = value.indexOf(delimiter) >= 0;
        boolean containsNewline = value.indexOf('\n') >= 0;
        boolean containsCarriageReturn = value.indexOf('\r') >= 0;
        boolean containsNullCharacter = value.indexOf(0) >= 0;
        boolean startsWithQuote = value.charAt(0) == '"';
        return containsDelimiter || containsNewline || containsCarriageReturn ||
                containsNullCharacter || startsWithQuote;
    }

    /**
     * Append a row value surrounded by quotes, omitting characters that should
     * not appear inside the quoted value.
     *
     * @param sb the destination buffer
     * @param value the value to append
     */
    private static void appendQuotedValue(StringBuilder sb, String value) {
        sb.append('"');
        int length = value.length();
        for (int j = 0; j < length; j += 1) {
            char c = value.charAt(j);
            if (c >= ' ' && c != '"') {
                sb.append(c);
            }
        }
        sb.append('"');
    }

    /**
     * Produce a JSONArray of JSONObjects from a comma delimited text string,
     * using the first row as a source of names.
     * @param string The comma delimited text.
     * @return A JSONArray of JSONObjects.
     * @throws JSONException if a called function fails
     */
    public static JSONArray toJSONArray(String string) throws JSONException {
        return toJSONArray(string, ',');
    }

    /**
     * Produce a JSONArray of JSONObjects from a comma delimited text string,
     * using the first row as a source of names.
     * @param string The comma delimited text.
     * @param delimiter custom delimiter char
     * @return A JSONArray of JSONObjects.
     * @throws JSONException if a called function fails
     */
    public static JSONArray toJSONArray(String string, char delimiter) throws JSONException {
        return toJSONArray(new JSONTokener(string), delimiter);
    }

    /**
     * Produce a JSONArray of JSONObjects from a comma delimited text string,
     * using the first row as a source of names.
     * @param x The JSONTokener containing the comma delimited text.
     * @return A JSONArray of JSONObjects.
     * @throws JSONException if a called function fails
     */
    public static JSONArray toJSONArray(JSONTokener x) throws JSONException {
        return toJSONArray(x, ',');
    }

    /**
     * Produce a JSONArray of JSONObjects from a comma delimited text string,
     * using the first row as a source of names.
     * @param x The JSONTokener containing the comma delimited text.
     * @param delimiter custom delimiter char
     * @return A JSONArray of JSONObjects.
     * @throws JSONException if a called function fails
     */
    public static JSONArray toJSONArray(JSONTokener x, char delimiter) throws JSONException {
        return toJSONArray(rowToJSONArray(x, delimiter), x, delimiter);
    }

    /**
     * Produce a JSONArray of JSONObjects from a comma delimited text string
     * using a supplied JSONArray as the source of element names.
     * @param names A JSONArray of strings.
     * @param string The comma delimited text.
     * @return A JSONArray of JSONObjects.
     * @throws JSONException if a called function fails
     */
    public static JSONArray toJSONArray(JSONArray names, String string) throws JSONException {
        return toJSONArray(names, string, ',');
    }

    /**
     * Produce a JSONArray of JSONObjects from a comma delimited text string
     * using a supplied JSONArray as the source of element names.
     * @param names A JSONArray of strings.
     * @param string The comma delimited text.
     * @param delimiter custom delimiter char
     * @return A JSONArray of JSONObjects.
     * @throws JSONException if a called function fails
     */
    public static JSONArray toJSONArray(JSONArray names, String string, char delimiter) throws JSONException {
        return toJSONArray(names, new JSONTokener(string), delimiter);
    }

    /**
     * Produce a JSONArray of JSONObjects from a comma delimited text string
     * using a supplied JSONArray as the source of element names.
     * @param names A JSONArray of strings.
     * @param x A JSONTokener of the source text.
     * @return A JSONArray of JSONObjects.
     * @throws JSONException if a called function fails
     */
    public static JSONArray toJSONArray(JSONArray names, JSONTokener x) throws JSONException {
        return toJSONArray(names, x, ',');
    }

    /**
     * Produce a JSONArray of JSONObjects from a comma delimited text string
     * using a supplied JSONArray as the source of element names.
     * @param names A JSONArray of strings.
     * @param x A JSONTokener of the source text.
     * @param delimiter custom delimiter char
     * @return A JSONArray of JSONObjects.
     * @throws JSONException if a called function fails
     */
    public static JSONArray toJSONArray(JSONArray names, JSONTokener x, char delimiter) throws JSONException {
        if (names == null || names.length() == 0) {
            return null;
        }
        JSONArray ja = new JSONArray();
        for (;;) {
            JSONObject jo = rowToJSONObject(names, x, delimiter);
            if (jo == null) {
                break;
            }
            ja.put(jo);
        }
        if (ja.length() == 0) {
            return null;
        }

        // The following block accounts for empty datasets (no keys or vals)
        if (ja.length() == 1) {
            JSONObject j = ja.getJSONObject(0);
            if (j.length() == 1) {
                String key = j.keys().next();
                if ("".equals(key) && "".equals(j.get(key))) {
                    return null;
                }
            }
        }
        return ja;
    }


    /**
     * Produce a comma delimited text from a JSONArray of JSONObjects. The
     * first row will be a list of names obtained by inspecting the first
     * JSONObject.
     * @param ja A JSONArray of JSONObjects.
     * @return A comma delimited text.
     * @throws JSONException if a called function fails
     */
    public static String toString(JSONArray ja) throws JSONException {
        return toString(ja, ',');
    }

    /**
     * Produce a comma delimited text from a JSONArray of JSONObjects. The
     * first row will be a list of names obtained by inspecting the first
     * JSONObject.
     * @param ja A JSONArray of JSONObjects.
     * @param delimiter custom delimiter char
     * @return A comma delimited text.
     * @throws JSONException if a called function fails
     */
    public static String toString(JSONArray ja, char delimiter) throws JSONException {
        JSONObject jo = ja.optJSONObject(0);
        if (jo != null) {
            JSONArray names = jo.names();
            if (names != null) {
                return rowToString(names, delimiter) + toString(names, ja, delimiter);
            }
        }
        return null;
    }

    /**
     * Produce a comma delimited text from a JSONArray of JSONObjects using
     * a provided list of names. The list of names is not included in the
     * output.
     * @param names A JSONArray of strings.
     * @param ja A JSONArray of JSONObjects.
     * @return A comma delimited text.
     * @throws JSONException if a called function fails
     */
    public static String toString(JSONArray names, JSONArray ja) throws JSONException {
        return toString(names, ja, ',');
    }

    /**
     * Produce a comma delimited text from a JSONArray of JSONObjects using
     * a provided list of names. The list of names is not included in the
     * output.
     * @param names A JSONArray of strings.
     * @param ja A JSONArray of JSONObjects.
     * @param delimiter custom delimiter char
     * @return A comma delimited text.
     * @throws JSONException if a called function fails
     */
    public static String toString(JSONArray names, JSONArray ja, char delimiter) throws JSONException {
        if (names == null || names.length() == 0) {
            return null;
        }
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < ja.length(); i += 1) {
            JSONObject jo = ja.optJSONObject(i);
            if (jo != null) {
                sb.append(rowToString(jo.toJSONArray(names), delimiter));
            }
        }
        return sb.toString();
    }
}
