package be.iminds.ilabt.jfed.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nullable;
import java.io.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

/**
 * LocalStringUtils
 */
public class TextUtil {
    private static final Logger LOG = LoggerFactory.getLogger(TextUtil.class);

    public static String printArray(String[] arr) {
        if (arr == null) return "null";
        String res = "[";
        boolean first = true;
        for (String a : arr) {
            if (!first)
                res += ", ";
            res += a;
            first = false;
        }
        res += "]";
        return res;
    }

    /**
     * @return the input string, with newlines inserted so that no line is longer than 'maxLineLen'
     */
    public static String wrap(String s, int maxLineLen) {
        if (s.length() < maxLineLen)
            return s;
        StringBuilder result = new StringBuilder();
        int pos = 0;

        while (pos < s.length()) {
            int newPos = s.indexOf('\n', pos);

            if (newPos < 0 || (newPos - pos) > maxLineLen) {
                result.append(s.substring(pos, Math.min(pos + maxLineLen, s.length())));
                if (pos + maxLineLen < s.length())
                    result.append('\n');

                pos += maxLineLen;
            } else {
                result.append(s.substring(pos, newPos + 1 /*newPos inclusive */));

                pos = newPos + 1;
            }
        }
        return result.toString();
    }

    /**
     * @return the input string, with newlines inserted so that no line is longer than 'maxLineLen'
     */
    public static String abbreviate(String s, int maxLineLen) {
        if (s == null)
            return null;
        if (s.length() < maxLineLen)
            return s;
        return s.substring(0, maxLineLen) + "...";
    }

    public static List<String> getLines(String in) {
        List<String> res = new ArrayList<>();
        Collections.addAll(res, in.split("\n"));
        return res;
    }

    public static String mkString(Collection<?> c, String sep) {
        String res = "";
        for (Object i : c) {
            if (!res.isEmpty()) res += sep;
            res += i.toString();
        }
        return res;
    }

    /**
     * @returns the boolean represented by this string or boolean, or null if not a boolean or string representation of a boolean
     */
    public static Boolean objectToBoolean(Object val) {
        if (val == null) return null;
        if (val instanceof String)
            return stringToBoolean((String) val);
        if (val instanceof Boolean)
            return (Boolean) val;
        return stringToBoolean(val+"");
    }

    /**
     * Create a String or null from an object.
     *
     * @param val        the possible string. May be null.
     * @param makeString if true, will make a string from val if it is non null, even if it is not of the String class. If false, return null if val is not a String.
     * @return
     */
    public static String objectToStringOrNull(@Nullable Object val, boolean makeString) {
        if (val == null) return null;
        if (val instanceof String)
            return (String) val;
        if (makeString)
            return val + "";
        else
            return null;
    }

    /**
     * @returns the boolean represented by this string, or null if not a boolean or if it is null
     */
    @Nullable
    public static Boolean stringToBoolean(@Nullable String val) {
        if (val == null)
            return null;
        val = val.trim().toLowerCase();
        switch (val) {
            case "true": return true;
            case "yes": return true;
            case "yep": return true; //seen in <link_attribute xmlns="http://www.protogeni.net/resources/rspec/ext/emulab/1" key="nomac_learning" value="yep" />
            case "1": return true;
            case "t": return true;
            case "y": return true;

            case "false": return false;
            case "no": return false;
            case "nope": return false;
            case "f": return false;
            case "n": return false;
            case "0": return false;

            default: return null;
        }
    }

    public static String exceptionToString(Throwable exception) {
        assert (exception != null);
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        exception.printStackTrace(pw);
        String stackTrace = sw.toString();
        return stackTrace;
    }

    /**
     * indent the lines in the provided string.
     * This is done by adding <count> spaces before each line.
     * <p>
     * TODO: this implementation can be improved by using something other than the BufferedReader
     *
     * @param count nr of lines to indent each line.
     */
    public static String indent(int count, String text) {
        if (text == null)
            return null;
        String spaces = "";
        for (int i = 0; i < count; i++)
            spaces += " ";
        char lastChar = text.isEmpty() ? ' ' : text.charAt(text.length() - 1);
        boolean endsWithNewLine = lastChar == '\n' || lastChar == '\r';
        String result = "";
        BufferedReader br = null;
        try {
            br = new BufferedReader(new StringReader(text));
            String line = br.readLine();
            while (line != null) {
                result += spaces + line + "\n"; //note: this replaces any line ending with \n
                line = br.readLine();
            }
        } catch (IOException e) {
            LOG.error("Got an IOException where there should never be one", e);
            throw new RuntimeException(e);
        } finally {
            if (br != null)
                try {
                    br.close();
                } catch (IOException e) {
                    LOG.error("Got an IOException where there should never be one", e);
                }
        }
        if (!endsWithNewLine && !result.isEmpty())
            return result.substring(0, result.length() - 1);
        return result;
    }
}