package utility;

import java.io.File;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;

public class Utils {
    private static String printable = "0123456789abcdefghijklmnopqrstuvwxyz" +
            "ABCDEFGHIJKLMNOPQRSTUVWXYZ`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?";

    static ConversionConstants hex = new ConversionConstants(2, 16);
    static ConversionConstants decimal = new ConversionConstants(3, 10);
    private static ConversionConstants octal = new ConversionConstants(3, 8);
    private static ConversionConstants binary = new ConversionConstants(8, 2);

    public static final int HEX = 16;
    public static final int DECIMAL = 10;
    public static final int OCTAL = 8;
    public static final int BINARY = 2;

    static public LinkedHashMap<String, HashMap<Integer, Object>> sortHashMapByValues(
            HashMap<String, HashMap<Integer, Object>> passedMapIn, Integer key0) {

        HashMap<String, HashMap<Integer, Object>> passedMapLocal = (HashMap) passedMapIn.clone();

        List mapKeys = new ArrayList();
        List mapValues = new ArrayList();

        for (Map.Entry<String, HashMap<Integer, Object>> me : passedMapLocal.entrySet()) {
            mapKeys.add(me.getKey());
            mapValues.add(me.getValue().get(key0));
        }

        Collections.sort(mapValues);
        Collections.sort(mapKeys);
        Collections.reverse(mapValues);

        LinkedHashMap<String, HashMap<Integer, Object>> sortedMap = new LinkedHashMap<>();

        Iterator valueIt = mapValues.iterator();
        while (valueIt.hasNext()) {
            Object val = valueIt.next();
            Iterator keyIt = mapKeys.iterator();

            while (keyIt.hasNext()) {
                Object key = keyIt.next();
                String comp1 = passedMapLocal.get(key).get(key0).toString();
                String comp2 = val.toString();

                if (comp1.equals(comp2)) {
                    sortedMap.put((String) key, passedMapLocal.get(key));
                    passedMapLocal.remove(key);
                    mapKeys.remove(key);
                    break;
                }
            }
        }
        return sortedMap;
    }

    static public LinkedHashMap sortHashMapByValuesDesc(
            HashMap<Integer, Long> passedMap) {

        HashMap<Integer, Long> passedMap1 = (HashMap) passedMap.clone();

        List mapKeys = new ArrayList(passedMap1.keySet());
        List mapValues = new ArrayList(passedMap1.values());
        Collections.sort(mapValues);
        Collections.sort(mapKeys);
        Collections.reverse(mapValues);

        LinkedHashMap sortedMap = new LinkedHashMap();

        Iterator valueIt = mapValues.iterator();
        while (valueIt.hasNext()) {
            Object val = valueIt.next();
            Iterator keyIt = mapKeys.iterator();

            while (keyIt.hasNext()) {
                Object key = keyIt.next();
                String comp1 = passedMap1.get(key).toString();
                String comp2 = val.toString();

                if (comp1.equals(comp2)) {
                    passedMap1.remove(key);
                    mapKeys.remove(key);
                    sortedMap.put(key, val);
                    break;
                }
            }
        }
        return sortedMap;
    }

    static public double round(double d, int decimalPlace) {
        BigDecimal bd;
        try {
            bd = new BigDecimal(Double.toString(d));
            bd = bd.setScale(decimalPlace, BigDecimal.ROUND_HALF_UP);
            return bd.doubleValue();
        } catch (NumberFormatException e) {
            return 0.0;
        }
    }

    static public String roundBytes(double bytes) {
        Double doubleBytes = bytes;
        Long integerBytes = (long) bytes;

        if (integerBytes < 102400) {
            Double tmp = Utils.round(doubleBytes, 0);
            return tmp.longValue() + "";
        } else if (integerBytes >= 102400 && integerBytes < 1024000) {
            Double tmp = Utils.round(doubleBytes / 1024, 0);
            return tmp.longValue() + "K";
        } else if (integerBytes >= 1024000) {
            Double tmp = Utils.round(doubleBytes / 1024 / 1024, 0);
            return tmp.longValue() + "M";
        } else {
            return "";
        }
    }

    public static String getTimeFormatting(double intervalSec) {
        String out = "";

        // Exit when intervalSec == 0
        if (intervalSec == 0.0) {
            return out;
        }

        LocalDateTime start = LocalDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.systemDefault());
        LocalDateTime end = LocalDateTime.ofInstant(Instant.ofEpochMilli((long) (intervalSec * 1000)), ZoneId.systemDefault());

        Duration duration = Duration.between(start, end);

        String hours = "";
        String minutes = "";
        String secunds = "";

        long intHours = duration.getSeconds()/3600;
        long intMin = duration.getSeconds()/60;

        if (intHours < 10) {
            hours = "0" + intHours;
        } else {
            hours = "" + intHours;
        }
        if (intMin < 10) {
            minutes = "0" + intMin;
        } else {
            minutes = "" + intMin;
        }
        if (duration.getSeconds() < 10) {
            secunds = "0" + duration.getSeconds();
        } else {
            secunds = "" + duration.getSeconds();
        }

        out = hours + ":" + minutes + ":" + secunds;

        return out;
    }

    static public double getScale(int scaleToggle, double percent) {
        /* < 30  => 2
         * 30-70  => 1
		 * > 70    => 0 */
        double tmp = 0.0;
        switch (scaleToggle) {
            case 0:
                tmp = 125 / (percent + 51);
                break;
            case 1:
                tmp = 147 / (percent + 51);
                break;
            case 2:
                tmp = 200 / (percent + 51);
                break;
            default:
                tmp = 130 / (percent + 51);
        }
        return tmp;
    }

    public static String convertToString(Object value) {
        StringBuffer out = new StringBuffer();
        if (value instanceof byte[]) {
            Byte[] useValue = null;
            byte[] bytes = (byte[]) value;
            useValue = new Byte[bytes.length];
            for (int m = 0; m < bytes.length; m++) {
                useValue[m] = bytes[m];
            }
            out.append(Utils.convertToString(useValue, Utils.HEX, false));
        } else if (value instanceof BigDecimal) {
            long l1 = ((BigDecimal) value).intValue();
            out.append(l1);
        } else {
            out.append(value);
        }
        return out.toString();
    }

    public static String convertToString(Byte[] data, int base, boolean showAscii) {
        // handle null
        if (data == null)
            return "null";

        StringBuffer buf = new StringBuffer();
        ConversionConstants convConst = getConstants(base);

        // Convert each byte and put into string buffer
        for (int i = 0; i < data.length; i++) {
            int value = data[i].byteValue();
            String s = null;

            // if user wants to see ASCII chars as characters,
            // see if this is one that should be displayed that way
            if (showAscii) {
                if (printable.indexOf((char) value) > -1) {
                    s = Character.valueOf((char) value) +
                            "          ".substring(10 - (convConst.width - 1));
                }
            }

            // if use is not looking for ASCII chars, or if this one is one that
            // is not printable, then convert it into numeric form
            if (s == null) {
                switch (base) {
                    case DECIMAL:
                        // convert signed to unsigned
                        if (value < 0)
                            value = 256 + value;
                        s = Integer.toString(value);
                        break;
                    case OCTAL:
                        s = Integer.toOctalString(value);
                        break;
                    case BINARY:
                        s = Integer.toBinaryString(value);
                        break;
                    case HEX:    // fall through to default
                    default:
                        s = Integer.toHexString(value);
                }
                // some formats (e.g. hex & octal) extend a negative number to multiple places
                // (e.g. FC becomes FFFC), so chop off extra stuff in front
                if (s.length() > convConst.width)
                    s = s.substring(s.length() - convConst.width);

                // front pad with zeros and add to output
                if (s.length() < convConst.width)
                    buf.append("00000000".substring(8 - (convConst.width - s.length())));
            }
            buf.append(s);
            buf.append("");    // always add spaces at end for consistancy
        }
        return buf.toString();
    }


    /*
     * Conversion Constants
     */
    static class ConversionConstants {
        int width;    // number of chars used to represent byte
        int radix;    // the base radix

        ConversionConstants(int w, int r) {
            width = w;
            radix = r;
        }
    }

    /**
     * Get the constants to use for the given base.
     */
    private static ConversionConstants getConstants(int base) {
        if (base == HEX) return hex;
        if (base == DECIMAL) return decimal;
        if (base == OCTAL) return octal;
        if (base == BINARY) return binary;
        return hex;    // default to hex if unknown base passed in
    }

    /**
     * Get levels of all nodes of sql plan tree.
     */
    static public HashMap<Long,Long> getLevels(HashMap<Long,Long> idParentId){
        long rootLevel = 1;
        HashMap<Long, Long> idLevel = new HashMap<Long, Long>();
        Set entries = idParentId.entrySet();
        Iterator it = entries.iterator();
        while (it.hasNext()) {
            Map.Entry entry = (Map.Entry) it.next();
            Long key = (Long) entry.getKey();
            if (key==0){
                idLevel.put(key, rootLevel);
            } else {
                idLevel.put(key, getLevel(idParentId,key));
            }
        }
        return idLevel;
    }

    /**
     * Get node level.
     */
    static private Long getLevel (HashMap<Long,Long> idParentId, Long id){
        long level = 0;
        Set entries = idParentId.entrySet();
        Iterator it = entries.iterator();
        while (it.hasNext()) {
            Map.Entry entry = (Map.Entry) it.next();
            Long key = (Long) entry.getKey();
            Long value = (Long) entry.getValue();
            if (key == id){
                if (value != -1){
                    if (value ==0){
                        level = 2 + getLevel(idParentId, value);
                    } else {
                        level = 1 + getLevel(idParentId, value);
                    }
                }
            }
        }

        return level;
    }

    public static void delete(File directory) {
        File[] contents = directory.listFiles();
        for(File file : contents) {
            if (file.isDirectory()) {
                delete(file);
                file.delete();
            } else {
                file.delete();
            }
        }
        directory.delete();
    }

    public static String getDateForLongShorted(long longDate, String dateFormat){
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(dateFormat);
        java.sql.Date dtDate= new java.sql.Date((longDate));
        return simpleDateFormat.format(dtDate);
    }
}
