import java.util.HashMap;
import java.util.*;
import java.nio.ByteBuffer;
import java.util.Stack;

public class Main
{
    public static byte[] nullCharBytes = {(byte)0xFF,(byte)0xFF};
    //public static String nullCharBytesString = "1111111111111111";
    public static void test(String text)
    {
        Map<Character, Integer> zahlen = Zählen.countEachLetter(text);
        Node wurzel = Node.erstellen(zahlen);

        System.out.println();
        System.out.println("Pre-Order Baum:");
        printBaum(wurzel);

        System.out.println();
        System.out.println("Anzahl der Zeichen:");
        Zählen.printZahl(zahlen);

        System.out.println();
        System.out.println("Binärcode der Zeichen:");
        codes(text);

        System.out.println();

        decodierungBaum(codierung(text,wurzel), wurzel);

        System.out.println(byteString_to_character(character_to_byteString(null)));
        System.out.println(byteString_to_character(character_to_byteString('b')));
        System.out.println(byteString_to_character(character_to_byteString('c')));
        System.out.println(byteString_to_character(character_to_byteString('d')));
        System.out.println(byteString_to_character(character_to_byteString('e')));
        System.out.println(byteString_to_character(character_to_byteString('f')));
        System.out.println(byteString_to_character(character_to_byteString('g')));
        System.out.println(byteString_to_character(character_to_byteString('h')));

        byteString_to_baum(baum_to_byteString(wurzel));
        printBaum(wurzel);
    }

    public static void test2()
    {
        Map<Character, Integer> zahlen = Zählen.countEachLetter("aaaabbc");
        Node wurzel = Node.erstellen(zahlen);
        printBaum(wurzel);
        //byteString_to_baum(baum_to_byteString(wurzel));
        HashMap<Character, String> codes = new HashMap<Character, String>();

        if(wurzel.getLeft() == null && wurzel.getRight() == null)
        {
            codes.put(wurzel.getBuchstabe(), "0");
        }
        else
        {
            generieren(codes,wurzel,"");
        }

        for (var entry : codes.entrySet()) 
        {
            System.out.println("->" + entry.getKey() + ", Code: " + entry.getValue());
        }

        printBaum(byteString_to_baum(baum_to_byteString(wurzel)));
    }

    public static void printBaum(Node baum)
    {
        if(baum == null)return;
        System.out.println("Buchstabe: " + baum.getBuchstabe() + ", Anzahl: " + baum.getAnzahl());
        printBaum(baum.getLeft());
        printBaum(baum.getRight());
    }

    public static void codes(String text)
    {
        Node wurzel = Node.erstellen(Zählen.countEachLetter(text));
        HashMap<Character, String> codes = new HashMap<Character, String>();

        if(wurzel.getLeft() == null && wurzel.getRight() == null)
        {
            codes.put(wurzel.getBuchstabe(), "0");
        }
        else
        {
            generieren(codes,wurzel,"");
        }

        for (var entry : codes.entrySet()) 
        {
            System.out.println("->" + entry.getKey() + ", Code: " + entry.getValue());
        }
    }

    public static void generieren(HashMap<Character, String> codes, Node node, String code)
    {
        if(node == null)return;
        System.out.println(node.getBuchstabe() +"  " +  code);
        if(node.getLeft() == null && node.getRight() == null)
        {
            codes.put(node.getBuchstabe(), code);

            return;
        }
        generieren(codes, node.getLeft(), code + "0");
        generieren(codes, node.getRight(), code + "1");
    }

    public static String codierung(String text, Node wurzel)
    {
        HashMap<Character, String> codes = new HashMap<Character, String>();

        if(wurzel.getLeft() == null && wurzel.getRight() == null)
        {
            codes.put(wurzel.getBuchstabe(), "0");
        }
        else
        {
            generieren(codes,wurzel,"");
        }
        String code = "";
        for (int i=0; i<text.length();i++)
        {
            char b = text.charAt(i);
            String c = codes.get(b);
            code += c;
        }
        System.out.println("Huffman-Codierung: " + code);
        return code;
    }

    public static String decodierungBaum(String code,Node baum)
    {
        Node current = baum;
        String decoded = "";

        boolean hatNurEinBuchstabenart = current.getRight() == null && current.getLeft() == null;
        if(hatNurEinBuchstabenart)
        {
            for(int i=0;i<code.length();i++)
            {
                decoded += baum.getBuchstabe();
            }
            System.out.println(decoded);
            return decoded;

        }

        for(int i =0;i<code.length();i++)
        {
            if(code.charAt(i) == '1')
            {
                current= current.getRight();
            }
            if(code.charAt(i) == '0')
            {
                current = current.getLeft();
            }
            if(current.getRight() == null && current.getLeft() == null)
            {
                decoded += current.getBuchstabe();
                current = baum;
            }
        }
        System.out.println(decoded);
        return decoded;
    }

    public static Byte ByteString_to_Byte (String byteString)
    {
        if(byteString.length() != 8)
        {
            System.out.println(">8");
            return null;
        }

        byte result = 0;
        for(byte i=0 ;i<8;i++)
        {
            if(byteString.charAt(7-i) == '1')result|=1<<i; //verschiebt die 1 um i binärpositionen nach links
            else if(byteString.charAt(7-i) != '0')
            {
                System.out.println("was flasch");
                return null;
            }
        }

        return result;
    }

    public static String Byte_to_ByteString (byte b)
    {
        String result = "";

        for(byte i=0;i<8;i++)
        {
            boolean bit= ((b>>(7-i))&1)==1; 
            result+= bit?'1': '0'; //falls bit wert 1 hat addiert es 1 zu dem string, wenn nicht dann addiertes es 0
        }

        return result;
    }

    public static String character_to_byteString (Character c)
    {

        byte[] b = nullCharBytes;

        if(c != null)
        {
            var byteBuffer = ByteBuffer.allocate(2);
            byteBuffer.putChar(c);
            b = byteBuffer.array();
        }

        String result = "";
        for(int i=0;i<b.length;i++)
        {
            result += Byte_to_ByteString(b[i]);
        }
        return result;
    }

    public static Character byteString_to_character (String byteString)
    {
        byte a = ByteString_to_Byte(byteString.substring(0,8));
        byte b = ByteString_to_Byte(byteString.substring(8,16));
        byte[] array = new byte[] {a,b};
        if(array[0] == nullCharBytes[0] && array[1] == nullCharBytes[1])return null;
        var byteBuffer = ByteBuffer.wrap(array);

        return byteBuffer.getChar(0);
    }

    public static String baum_to_byteString (Node baum)
    {
        if(baum == null)return "";

        String byteString = character_to_byteString(baum.getBuchstabe());
        byteString += baum_to_byteString(baum.getLeft());
        byteString += baum_to_byteString(baum.getRight());
        return byteString;
    }

    public static Node byteString_to_baum (String s)
    {
        Node root = null;
        Stack<Node> äste = new Stack<Node>();
        for(int i=0;i<s.length();i+=16)
        {
            String characterString = s.substring(i,i+16);
            Character c = byteString_to_character(characterString);
            Node node = new Node();
            node.setBuchstabe(c);
            if(i == 0)root = node;

            if(äste.isEmpty() == false)
            {
                Node elternAst = äste.peek();

                elternAst.add(node);
                if(elternAst.hatPlatzKinder() == false)
                {
                    äste.pop();
                }
            }

            if(c == null)
            {

                äste.push(node);
                
            }

        }
        
        return root;
    }
    
    public static Integer byteString_to_int(String byteString)
    {
        if(byteString.length() != 32)
        {
            System.out.println(">32");
            return null;
        }

        int result = 0;
        for(int i=0 ;i<32;i++)
        {
            if(byteString.charAt(31-i) == '1')result|=1<<i; //verschiebt die 1 um i binärpositionen nach links
            else if(byteString.charAt(31-i) != '0')
            {
                System.out.println("was flasch");
                return null;
            }
        }

        return result;
    }
    
    public static String Int_to_ByteString (int a)
    {
        String result = "";

        for(int i=0;i<32;i++)
        {
            boolean bit= ((a>>(31-i))&1)==1; 
            result+= bit?'1': '0'; //falls bit wert 1 hat addiert es 1 zu dem string, wenn nicht dann addiertes es 0
        }

        return result;
    }

    
}