package com.jens.rhasspy.shoppinglist;


import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.security.SecureRandom;
import java.sql.Time;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.TimeZone;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

public class Diverse
{
    public final static String networkSeparatorCommunication = ";commSep";
    public final static String networkSeparatorSettings = ";commSett";

    public static enum cacheObjectsEnum { all, devices, commands, rooms, rules, users, houses, nodes, sensors, userDevices };

    public enum SolarEvent {sunrise, sunset};

    public static final String lineSeparator = System.getProperty("line.separator");

    public static String intToOpenClosed(int value)
    {
        if(value == 1)
            return "closed";
        else if(value == 0)
            return "open";
        else
            return "unknown";
    }

    public static String intToWaterYesNo(int value)
    {
        if(value == 1)
            return "water";
        else if(value == 0)
            return "dry";
        else
            return "unknown";
    }

    public static String intToSmokeYesNo(int value)
    {
        if(value == 1)
            return "smoke";
        else if(value == 0)
            return "no smoke";
        else
            return "unknown";
    }

    public static String getEnabledDisabledString(boolean value)
    {
        if(value)
            return "enabled";
        else
            return "disabled";
    }

    public static Calendar calendarFromLong(long source)
    {
        Calendar cal = Calendar.getInstance();
        cal.setTimeInMillis(source);
        return cal;
    }

    public static String getOnOffString(boolean value)
    {
        if(value)
            return "on";
        else
            return "off";
    }

    public static String getIniLikeValue(String fullText, String searchObject, String separator)
    {
        fullText = fullText.trim();

        if (fullText.toLowerCase().contains(searchObject.toLowerCase()))
        {
            if (fullText.toLowerCase().contains(separator.toLowerCase()))    // multiple values
            {
                int start = fullText.toLowerCase().indexOf(searchObject.toLowerCase() + "=") + searchObject.length() + 1;
                int end = fullText.toLowerCase().indexOf(separator.toLowerCase(), start);

                if (end == -1)    // if the last index in fullText
                    return fullText.substring(start);
                else
                    return fullText.substring(start, end);
            }
            else                                // only one value
            {
                return fullText.substring(fullText.indexOf("=") + 1);
            }
        }
        else
        {
            Miscellaneous.logEvent("Index " + searchObject + " cannot be found in full text.", 5);
            return null;
        }
    }

    public static String addOrReplaceIniLikeValue(String fullText, String key, String value, String separator)
    {
        fullText = fullText.trim();

        if (fullText.startsWith(key + "=") | fullText.contains(separator + key + "="))
        {
            if (fullText.contains(separator))    // multiple values
            {
                int start = fullText.indexOf(key + "=") + key.length() + 1;
                int end = fullText.indexOf(separator, start);

                String oldValue;
                if (end == -1)    // if the last index in fullText
                    oldValue = fullText.substring(start);
                else
                    oldValue = fullText.substring(start, end);

                return fullText.replace(key + "=" + oldValue, key + "=" + value);
            }
            else                                // only one value
            {
                return key + "=" + value;
            }
        }
        else
        {
            if(fullText.length() > 0)
                return fullText + separator + key + "=" + value;
            else
                return key + "=" + value;
        }
    }

    public static String removeIniLikeValue(String fullText, String key, String separator)
    {
        fullText = fullText.trim();
        String toBeRemoved = getIniLikeValue(fullText, key, separator);

        if(toBeRemoved != null && fullText.contains(toBeRemoved))
        {
            if(fullText.contains(";"))
            {
                if(fullText.contains(";" + toBeRemoved))
                    return fullText.replace(";" + toBeRemoved, "");
                else
                    return fullText.replace(toBeRemoved + ";", "");
            }
            else
                return "";
        }
        else
            return fullText;
    }

    public static String updateIniLikeValue(String fullText, String key, String value, String separator)
    {
        if (fullText.contains(key))
        {
            if (fullText.contains(separator))    // multiple values
            {
                String[] lines = fullText.split(separator);
                for(int i=0; i < lines.length; i++)
                {
                    if(lines[i].startsWith(key))
                    {
                        int equalsPosition = lines[i].indexOf(key + "=") + key.length() + 1;
                        lines[i] = lines[i].substring(0, equalsPosition) + value;
                        break;
                    }
                }

                return Diverse.arrayImplode(separator, lines);
            }
            else                                // only one value
            {
                return fullText + separator + key + "=" + value;
            }
        }
        else
        {
            return fullText + separator + key + "=" + value;
        }
    }

    public static int boolToInt(boolean in)
    {
        if (in)
            return 1;
        else
            return 0;
    }

    public static boolean intToBool(int in)
    {
        if (in > 0)
            return true;
        else
            return false;

//        ClientServerSplit.logEvent("Error transforming int to bool.", 3);
//        return false;
    }

    public static boolean isNumeric(String stringToCheck)
    {
        String character;

        for(int i = 0; i < stringToCheck.length(); i++)
        {
            character = stringToCheck.substring(i, i + 1);

            if(!character.matches("[1234567890]") && !character.equals(".") && !character.equals(","))
                return false;
        }

        return true;
    }

    public static boolean arraySearch(String[] haystack, String needle, boolean caseSensitive, boolean matchFullLine)
    {
        if(matchFullLine)
        {
            if(caseSensitive)
            {
                for (String s : haystack)
                {
                    if (s.equals(needle))
                        return true;
                }
            }
            else
            {
                for (String s : haystack)
                {
                    if (s.toLowerCase().equals(needle.toLowerCase()))
                        return true;
                }
            }
        }
        else
        {
            if(caseSensitive)
            {
                for (String s : haystack)
                {
                    if (s.contains(needle))
                        return true;
                }
            }
            else
            {
                for (String s : haystack)
                {
                    if (s.toLowerCase().contains(needle.toLowerCase()))
                        return true;
                }
            }
        }

        return false;
    }

    public static boolean arraySearch(ArrayList<String> requestList, String needle, boolean caseSensitive, boolean matchFullLine)
    {
        return arraySearch(requestList.toArray(new String[requestList.size()]), needle, caseSensitive, matchFullLine);
    }

    public static boolean arraySearch(cacheObjectsEnum[] haystack, cacheObjectsEnum needle)
    {
        if (needle != null)
        {
            for (cacheObjectsEnum c : haystack)
            {
                if (c.equals(needle))
                    return true;
            }
        }
        else
            Miscellaneous.logEvent("Needle should not be null in function arraySearch()", 5);

        return false;
    }

    public static String arrayImplode(String glue, String... values)
    {
        String returnString = "";

        for (String s : values)
            returnString += s + glue;

        if (returnString.length() > 0)
            returnString = returnString.substring(0, returnString.length() - glue.length());

        return returnString;
    }

    public static String arrayImplode(String glue, ArrayList<String> arrayList)
    {
        String returnString = "";

        for (Object s : arrayList)
            returnString += s + glue;

        if (returnString.length() > 0)
            returnString = returnString.substring(0, returnString.length() - glue.length());

        return returnString;
    }

    public static String arrayImplode(String glue, Object[] array)
    {
        String returnString = "";

        for (Object s : array)
            returnString += s.toString() + glue;

        if (returnString.length() > 0)
            returnString = returnString.substring(0, returnString.length() - glue.length());

        return returnString;
    }

    public static String arrayImplode(String glue, HashMap<String, String> map)
    {
        String returnString = "";

        for (String key : map.keySet())
            returnString += map.get(key) + glue;

        if (returnString.length() > 0)
            returnString = returnString.substring(0, returnString.length() - glue.length());

        return returnString;
    }

    public static String getSystemTimezone()
    {
//		https://coderanch.com/t/386398/java/System-Timezone
        TimeZone tz = Calendar.getInstance().getTimeZone();
//		System.out.println(tz.getDisplayName());// (i.e. Moscow Standard Time)
        return tz.getID();

//		Should return something like this: Asia/Calcutta
    }

    public static String createPassword(int length)
    {
        final String allowedChars = "0123456789abcdefghijklmnopqrstuvwABCDEFGHIJKLMNOP!§$%&?*+#";
        SecureRandom random = new SecureRandom();
        StringBuilder pass = new StringBuilder(length);
        for (int i = 0; i < length; i++)
        {
            pass.append(allowedChars.charAt(random.nextInt(allowedChars.length())));
        }
        return pass.toString();
    }

    public static Element getXmlTree(String inputString) throws SAXException, IOException, ParserConfigurationException
    {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = factory.newDocumentBuilder();

//		Create a Document from a file or stream
		/*
		StringBuilder xmlStringBuilder = new StringBuilder();
		xmlStringBuilder.append("<?xml version="1.0"?> <class> </class>");
		ByteArrayInputStream input = new ByteArrayInputStream(xmlStringBuilder.toString().getBytes("UTF-8"));
		*/
//		Document doc = builder.parse(input);
        Document doc = builder.parse(new InputSource(new StringReader(inputString)));

        Element rootElement = doc.getDocumentElement();

        return rootElement;
	/*
//		Examine attributes

		//returns specific attribute
		root.getAttribute("attributeName");

		//returns a Map (table) of names/values
		root.getAttributes();

//		Examine sub-elements

		//returns a list of subelements of specified name
		root.getElementsByTagName("subelementName");

		//returns a list of all child nodes
		root.getChildNodes();
	*/
    }

    public static String xmlToString(Node node, boolean omitXmlDeclaration, boolean prettyPrint)
    {
        if (node == null)
        {
            throw new IllegalArgumentException("node is null.");
        }

        try
        {
            // Remove unwanted whitespaces
            node.normalize();
            XPath xpath = XPathFactory.newInstance().newXPath();
            XPathExpression expr = xpath.compile("//text()[normalize-space()='']");
            NodeList nodeList = (NodeList) expr.evaluate(node, XPathConstants.NODESET);

            for (int i = 0; i < nodeList.getLength(); ++i)
            {
                Node nd = nodeList.item(i);
                nd.getParentNode().removeChild(nd);
            }

            // Create and setup transformer
            Transformer transformer = TransformerFactory.newInstance().newTransformer();
            transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");

            if (omitXmlDeclaration == true)
            {
                transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
            }

            if (prettyPrint == true)
            {
                transformer.setOutputProperty(OutputKeys.INDENT, "yes");
                transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
            }

            // Turn the node into a string
            StringWriter writer = new StringWriter();
            transformer.transform(new DOMSource(node), new StreamResult(writer));
            return writer.toString();
        }
        catch (TransformerConfigurationException e)
        {
            e.printStackTrace();
        }
        catch (TransformerException e)
        {
            throw new RuntimeException(e);
        }
        catch (XPathExpressionException e)
        {
            throw new RuntimeException(e);
        }

        return null;
    }


    public static String getStackTrace()
    {
        StringBuilder trace = new StringBuilder();
        for (StackTraceElement ste : Thread.currentThread().getStackTrace())
            trace.append(ste + Diverse.lineSeparator);

        trace.delete(trace.length(), trace.length());

        return trace.toString();
    }

    public static String getStackTraceAsString(Throwable aThrowable)
    {
        Writer result = new StringWriter();
        PrintWriter printWriter = new PrintWriter(result);
        aThrowable.printStackTrace(printWriter);
        return result.toString();
    }

    /**
     * Defines a custom format for the stack trace as String.
     */
    public static String getCustomStackTrace(Throwable aThrowable)
    {
        //add the class name and any message passed to constructor
        StringBuilder result = new StringBuilder( "Stacktrace: " );
        result.append(aThrowable.toString());
        String NEW_LINE = System.getProperty("line.separator");
        result.append(NEW_LINE);

        //add each element of the stack trace
        for (StackTraceElement element : aThrowable.getStackTrace())
        {
            result.append(element);
            result.append(NEW_LINE);
        }
        return result.toString();
    }

    public static ArrayList<Element> getSubElements(Node node, String parentName, String childName)
    {
        ArrayList<Element> returnList = new ArrayList<Element>();
        NodeList nodeElements = ((Element)node).getElementsByTagName("houses");
        for(int i = 0; i < nodeElements.getLength(); i++)
        {
            if (nodeElements.item(i).getNodeType() == Node.ELEMENT_NODE)
            {
                NodeList childElements = nodeElements.item(i).getChildNodes();
                for (int j = 0; j < childElements.getLength(); j++)
                {
                    Element element = (Element) childElements.item(j);
//					HouseTemplate house = HouseTemplate.fromXmlStringStatic(Diverse.getXmlFull(element));
                    returnList.add(element);
                }
            }
        }

		/*ArrayList<Element> returnList = new ArrayList<>();

		NodeList children = ((Element)node).getElementsByTagName(parentName);

		for(int i=0; i < children.getLength(); i++)
		{
			returnList.add((Element)children.item(i));
		}*/

        return returnList;
    }

    public static double round(double value, int places)
    {
        if (places < 0) throw new IllegalArgumentException();

        BigDecimal bd = new BigDecimal(Double.toString(value));
        bd = bd.setScale(places, RoundingMode.HALF_UP);
        return bd.doubleValue();
    }

    public static Class getCallerClass()
    {
        StackTraceElement[] stElements = Thread.currentThread().getStackTrace();
        for (int i=1; i<stElements.length; i++)
        {
            StackTraceElement ste = stElements[i];
            if (!ste.getClassName().equals(Diverse.class.getName()) && ste.getClassName().indexOf("java.lang.Thread")!=0)
            {
                return ste.getClass().getDeclaringClass();
            }
        }
        return null;
    }

    public static Class getCallerCallerClassName()
    {
        StackTraceElement[] stElements = Thread.currentThread().getStackTrace();
        String callerClassName = null;
        for (int i=1; i<stElements.length; i++)
        {
            StackTraceElement ste = stElements[i];
            if (!ste.getClassName().equals(Diverse.class.getName())&& ste.getClassName().indexOf("java.lang.Thread")!=0)
            {
                if (callerClassName==null)
                {
                    callerClassName = ste.getClassName();
                }
                else if (!callerClassName.equals(ste.getClassName()))
                {
                    try
                    {
                        Class returnClass = Class.forName(ste.getClassName());
                        return returnClass;
                    }
                    catch(Exception e)
                    {}
//                    return ste.getClassName();
                }
            }
        }
        return null;
    }
    
    public static int countOccurences(String input, String needle)
    {
    	String out = input.replace (needle, "");
    	int lenDiff = input.length () - out.length ();
    	return lenDiff / needle.length();
    }
}