import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.util.*;
import java.util.List;

import org.knowm.xchart.*;
import org.knowm.xchart.style.Styler;

import static java.util.Collections.sort;

public class TypeSplitter {
    //počet výskytov jednotlivých typov tokenov
    private List<Integer> tokenTypesCount = new ArrayList<>();
    //počet výskytov jednotlivých tokenov
    private Map<String, Integer> tokensCount = new HashMap<>();
    //počet výskytov jednotlivých medzier (resp. bielych znakov/postupnosti bielych znakov)
    private Map<String, Integer> tokensCountWhiteSpaces = new HashMap<>();
    //pole jednotlivých tokenov, teda  celý text pretransformovaný na pole tokenov
    private List<String> tokens = new ArrayList<>();

    private String path;
    public TypeSplitter(String text, String workingPath) {
        this.path = workingPath;
        for (int i = 0; i < TokenType.values().length; i++) tokenTypesCount.add(0);
        StringBuilder sb = new StringBuilder(text);
        CreateToken ct = new CreateToken();
        for (int i = 0; i < sb.length(); i++) {
            if (ct.addChar(sb.charAt(i))) {
                // Ak sa podarilo pridať znak, pokračuj
            } else {
                // Ak nie, token je hotový
                if (ct.getTokenType() == TokenType.NUMBER ||
                        ct.getTokenType() == TokenType.COMMA ||
                        ct.getTokenType() == TokenType.SEMICOLON ||
                        ct.getTokenType() == TokenType.DOT ||
                        ct.getTokenType() == TokenType.ELLIPSIS ||
                        ct.getTokenType() == TokenType.EXCLAMATION_MARK ||
                        ct.getTokenType() == TokenType.QUESTION_MARK ||
                        ct.getTokenType() == TokenType.WORD
                )
                tokenTypesCount.set(ct.getTokenType().getValue(), tokenTypesCount.get(ct.getTokenType().getValue()) + 1);

                String token = ct.getToken();
                if (ct.getTokenType() == TokenType.WORD) {
                    token = token.toLowerCase(); // zmení iba slová na malé písmen
                }
                if (ct.getTokenType() == TokenType.NUMBER ||
                        ct.getTokenType() == TokenType.COMMA ||
                        ct.getTokenType() == TokenType.SEMICOLON ||
                        ct.getTokenType() == TokenType.DOT ||
                        ct.getTokenType() == TokenType.ELLIPSIS ||
                        ct.getTokenType() == TokenType.EXCLAMATION_MARK ||
                        ct.getTokenType() == TokenType.QUESTION_MARK ||
                        ct.getTokenType() == TokenType.WORD
                ) {

                    tokens.add(token);
                    if (tokensCount.containsKey(token)) {
                        tokensCount.put(token, tokensCount.get(token) + 1);
                    } else {
                        tokensCount.put(token, 1);
                    }
                }
                if (ct.getTokenType() == TokenType.SPACE) {
                    if (tokensCountWhiteSpaces.containsKey(token)) {
                        tokensCountWhiteSpaces.put(token, tokensCountWhiteSpaces.get(token) + 1);
                    } else {
                        tokensCountWhiteSpaces.put(token, 1);
                    }
                }/* else {
                    if (tokensCount.containsKey(token)) {
                        //tokensCount.put(token, tokensCount.get(token) + 1);
                    } else {
                        //tokensCount.put(token, 0);
                    }
                }*/
                ct.reset();
                i--;
            }
        }
    }
    //Funkcia, ktorá mi vráti počet výskytov jednotlivých typov tokenov
    public List<Integer> getTokenTypesCount() {
        return tokenTypesCount;
    }
    //Funkcia, ktorá mi vráti mapu tokenov spolu s počtom ich výskytu
    public Map<String, Integer> getTokensCount() {
        return tokensCount;
    }
    //Funkcia, ktorá mi vráti pole tokenov (text) bez bielych znakov
    public List<String> getTokensWithoutWhiteSpace() {
        List<String> tokensWithoutWhiteSpace = new ArrayList<>();
        for (String token : tokens) {
            if (!token.trim().isEmpty()) {
                tokensWithoutWhiteSpace.add(token);
            }
        }
        return tokensWithoutWhiteSpace;
    }
    public void printTokensCountSorted(OutputStream os, Integer range) throws IOException {
        // Táto metóda vypíše počet výskytov jednotlivých tokenov zoradených podľa počtu výskytov zostupne, bez bielych znakov
        // Vytvorenie zoznamu z mapy
        List<Map.Entry<String, Integer>> list = new ArrayList<>(tokensCount.entrySet());

        // Zoradenie zoznamu podľa hodnoty (počtu výskytov)
        list.sort((entry1, entry2) -> entry2.getValue().compareTo(entry1.getValue()));

        // Výpis zoradeného zoznamu
        os.write(("Total tokens count: " + tokens.size() + "\n").getBytes());
        os.write(("Total unique tokens count: " + list.size() + "\n").getBytes());
        int tmp = 1;
        List<String> xData = new ArrayList<>();
        List<Integer> yData = new ArrayList<>();
        for (Map.Entry<String, Integer> entry : list) {
            xData.add(entry.getKey());
            yData.add(entry.getValue());
            String displayKey;
            if (entry.getKey().trim().isEmpty()) {
                StringBuilder ws = new StringBuilder("WS[");
                for (int i = 0; i < entry.getKey().length(); i++) {
                    ws.append((int)entry.getKey().charAt(i));
                    if (i < entry.getKey().length() - 1) ws.append(";");
                }
                ws.append("]");
                displayKey = ws.toString();
            } else {
                displayKey = entry.getKey();
            }

            String line = "#" + tmp + ": [" + displayKey + "] => " + entry.getValue() + "\n";
            os.write(line.getBytes());
            //System.out.print(line); // Voliteľne aj na konzolu

            //System.out.println("#" + tmp + ": [" + displayKey + "] => " + entry.getValue());
            tmp++;
        }
        os.flush();


        CategoryChart chart = new CategoryChartBuilder()
                .width(900)
                .height(600)
                .title("Graf frekvencií prvých 50 slov")
                .xAxisTitle("Tokeny")
                .yAxisTitle("Počet výskytov")
                .build();

        chart.getStyler().setXAxisLabelRotation(90); // 90 stupňov = vertikálne
        chart.getStyler().setLegendPosition(Styler.LegendPosition.InsideNE);
        range = Math.min(range, xData.size());
        Color[] colors = new Color[xData.subList(0,range).size()];
        for (int i = 0; i < xData.subList(0,range).size(); i++) {
            if (coinainsOnlyLetters(xData.get(i))) {
                colors[i] = Color.GREEN;       // písmená = hodnoty zelené
            } else if (containsOnlyDigits(xData.get(i))) {
                colors[i] = Color.ORANGE;    // čísla
            } else if (containsOnlyValidInterpunction(xData.get(i))){
                colors[i] = Color.BLUE;     // interpunkcia
            } else {
                colors[i] = Color.RED;      // ostatné
            }
        }

        // Pridanie dát
        chart.addSeries("Frekvencia slov z celkového počtu " + tokens.size(), xData.subList(0,range), yData.subList(0,range));

        // Alebo uloženie ako PNG
        try {
            BitmapEncoder.saveBitmap(chart, path + "/graf.png", BitmapEncoder.BitmapFormat.PNG);
        } catch (Exception e) {
            e.printStackTrace();
        }


    }
    public void printWhiteTokensCountSorted(OutputStream os) throws IOException {
        // Táto metóda vypíše počet výskytov jednotlivých tokenov (bielych) zoradených podľa počtu výskytov zostupne, bez bielych znakov
        // Vytvorenie zoznamu z mapy
        List<Map.Entry<String, Integer>> list = new ArrayList<>(tokensCountWhiteSpaces.entrySet());

        // Zoradenie zoznamu podľa hodnoty (počtu výskytov)
        list.sort((entry1, entry2) -> entry2.getValue().compareTo(entry1.getValue()));

        // Výpis zoradeného zoznamu
        os.write(("Total tokens count: " + tokens.size() + "\n").getBytes());
        os.write(("Total unique tokens count: " + list.size() + "\n").getBytes());
        int tmp = 1;
        for (Map.Entry<String, Integer> entry : list) {
            String displayKey;
            if (entry.getKey().trim().isEmpty()) {
                StringBuilder ws = new StringBuilder("WS[");
                for (int i = 0; i < entry.getKey().length(); i++) {
                    ws.append((int)entry.getKey().charAt(i));
                    if (i < entry.getKey().length() - 1) ws.append(";");
                }
                ws.append("]");
                displayKey = ws.toString();
            } else {
                displayKey = entry.getKey();
            }

            String line = "#" + tmp + ": [" + displayKey + "] => " + entry.getValue() + "\n";
            os.write(line.getBytes());
            //System.out.print(line); // Voliteľne aj na konzolu

            tmp++;
        }
        os.flush();
    }

    public void printTokenTypesCountSorted(OutputStream os) throws IOException {
        // Vytvoríme mapu TokenType -> počet
        Map<TokenType, Integer> map = new HashMap<>();
        for (int i = 0; i < tokenTypesCount.size(); i++) {
            map.put(TokenType.values()[i], tokenTypesCount.get(i));
        }

        // Zoradíme podľa počtu zostupne
        List<Map.Entry<TokenType, Integer>> sortedList = new ArrayList<>(map.entrySet());
        sortedList.sort((e1, e2) -> e2.getValue().compareTo(e1.getValue())); // zostupne podľa hodnoty

        // Zapíšeme do OutputStream
        os.write(("Total tokens count: " + tokens.size() + "\n").getBytes());
        for (Map.Entry<TokenType, Integer> entry : sortedList) {
            String line = "Token type " + entry.getKey() + ": " + entry.getValue() + "\n";
            os.write(line.getBytes());
        }

        os.flush(); // Dôležité
    }

    public Map<String, Integer> getNeighborCount(List<String> tokens) {
        Map<String, Integer> neighborCount = new HashMap<>();

        for (int i = 0; i < tokens.size()-1; i++) {
            // Skontrolujeme, či je token biely znak, alebo prázdny
            if (tokens.get(i).trim().isEmpty() || tokens.get(i + 1).trim().isEmpty()) {
                continue; // Ignorujeme biele znaky
            }
            String token = "[" + tokens.get(i) + "]_[" + tokens.get(i + 1) + "]";
            // Zvýšime počet výskytov tokenu
            if (neighborCount.containsKey(token)) {
                neighborCount.put(token, neighborCount.get(token) + 1);
            } else {
                neighborCount.put(token, 1);
            }
        }
        return neighborCount;
    }

    public String printNeighborCount() {
        StringBuilder sb = new StringBuilder();
        for (Map.Entry<String, Integer> entry : getNeighborCount(getTokensWithoutWhiteSpace()).entrySet()) {
            sb.append(entry.getKey()).append(": ").append(entry.getValue()).append("\n");
        }
        return sb.toString();
    }
    public void printNeighborCountSortedByValue(OutputStream os) throws IOException {
        StringBuilder sb = new StringBuilder();
        Map<String, Integer> neighborCount = getNeighborCount(getTokensWithoutWhiteSpace());
        neighborCount.entrySet().stream()
                .sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
                .forEach(entry -> sb.append(entry.getKey()).append(": ").append(entry.getValue()).append("\n"));
        sb.toString();
        os.write(("Total unique pairs count: " + neighborCount.size() + "\n").getBytes());
        os.write(sb.toString().getBytes());
        os.flush();
    }
    public void printSentenceStatistic(OutputStream os) throws IOException {
        Map<Integer, Integer> oznamovacie = new HashMap<>();
        Map<Integer, Integer> opytovacie = new HashMap<>();
        Map<Integer, Integer> rozkazovacie = new HashMap<>();

        List<String> currentSentence = new ArrayList<>();
        List<Sentence> sentences = new ArrayList<>();
        int sentenceCounter = 0;
        for (String token : tokens) {
            if (token.trim().isEmpty() || token.equals(" ") || token.equals("")) {
                continue; // Ignorujeme biele znaky
            }
            currentSentence.add(token);

            // Kontrola, či token je koncový znak vety
            if (token.equals(".") || token.equals("?") || token.equals("!")) {
                int pocetSlov = currentSentence.size(); // počet "slov" vrátane koncového znaku

                if (token.equals(".")) {
                    oznamovacie.put(pocetSlov, oznamovacie.getOrDefault(pocetSlov, 0) + 1);
                } else if (token.equals("?")) {
                    opytovacie.put(pocetSlov, opytovacie.getOrDefault(pocetSlov, 0) + 1);
                } else if (token.equals("!")) {
                    rozkazovacie.put(pocetSlov, rozkazovacie.getOrDefault(pocetSlov, 0) + 1);
                }
                sentenceCounter++;

                //System.out.println(currentSentence.size() + "=>" + currentSentence);
                sentences.add(new Sentence(currentSentence, sentenceCounter));
                currentSentence.clear();
            }
        }

        //System.out.println("Oznamovacie vety: " + oznamovacie);
        //System.out.println("Opytovacie vety: " + opytovacie);
        //System.out.println("Rozkazovacie vety: " + rozkazovacie);

        // zápis do OutputStream (napr. System.out)
        os.write(("Total sentences count: " + sentences.size() + "\n").getBytes());
        Collections.sort(sentences);
        for (Sentence sentence : sentences) {
            os.write(("#" + sentence.getId() + " | T: " + sentence.getWordCount() + " | I: " + sentence.getPunctuationCount() + " | "+ sentence.toString() + "\n").getBytes());
        }
        os.flush();
    }
    public String printNeighborCountSortedByKey() {
        StringBuilder sb = new StringBuilder();
        Map<String, Integer> neighborCount = getNeighborCount(getTokensWithoutWhiteSpace());
        neighborCount.entrySet().stream()
                .sorted(Map.Entry.comparingByKey())
                .forEach(entry -> sb.append(entry.getKey()).append(": ").append(entry.getValue()).append("\n"));
        return sb.toString();
    }
    public List<String> getTokens() {
        return tokens;
    }
    private boolean coinainsOnlyLetters(String str) {
        for (char c : str.toCharArray()) {
            if (!Character.isLetter(c)) {
                return false;
            }
        }
        return true;
    }
    private boolean containsOnlyDigits(String str) {
        for (char c : str.toCharArray()) {
            if (!Character.isDigit(c)) {
                return false;
            }
        }
        return true;
    }
    private boolean containsOnlyValidInterpunction(String str) {
        String validInterpunction = ".,;:!?";
        for (char c : str.toCharArray()) {
            if (validInterpunction.indexOf(c) == -1) {
                return false;
            }
        }
        return true;
    }
}
