package tourplaner.viewmodels;

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.image.Image;
import tourplaner.business.*;
import tourplaner.ui.AlertHelper;
import tourplaner.object.Log;
import tourplaner.object.Tour;
import tourplaner.ui.ProgressBar;

import java.awt.*;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

public class ViewModel {
    //Tour
    private final ObservableList<Tour> tourData = FXCollections.observableArrayList();
    private ObservableList<String> tourNamen = FXCollections.observableArrayList();
    private Tour selectedTour;
    private String neueTourName, neueTourStart, neueTourZiel, neueBemerkung;
    //Log
    private final ObservableList<Log> logData = FXCollections.observableArrayList();
    private Log selectedLog;
// Suche
    private boolean sucheAktiv;


    public Image getImage(String tourname){
        return new Image( ConfigHelper.getIniString(ConfigHelper.getStandartConfig(), "map", "file_pre") + TourPlaner.getImagePath(tourname));
    }

    /**
     * Bearbeitet eine bereits bestehende Tour
     * prüft ob eine tour ausgewählt ist
     */
    public void editTour() throws IOException {
        if (this.selectedTour == null){
            AlertHelper.warn(ConfigHelper.getLangIniString("achtung"),
                    ConfigHelper.getLangIniString("keinetourselected"),
                    ConfigHelper.getLangIniString("logtournotselectedmsg"));
        }else {
            AtomicInteger index = new AtomicInteger(-1);
            AtomicInteger aktIndex = new AtomicInteger(-1);
            this.tourData.forEach(s -> {
                index.getAndIncrement();
                if (s.getName().equals(this.selectedTour.getName())) {
                    aktIndex.set(index.intValue());
                }
            });
            if(aktIndex.intValue() != -1){

                if(tourNameInputDuplicatCheck(this.selectedTour.getName(), false)) {
                    if (tourStartInput(this.selectedTour.getStart())) {
                        if (tourZielInput(this.selectedTour.getZiel())) {

                            this.tourData.removeIf(tour -> tour.getName().equals(this.selectedTour.getName()));
                            this.tourNamen.removeIf(tour -> tour.equals(this.selectedTour.getName()));
                            Tour newTour = new Tour(this.neueTourName, "TBD", "TBD", 0, this.neueTourStart, this.neueTourZiel);
                            this.tourData.add(newTour);
                            this.tourNamen.add(this.neueTourName);
                            LogHelper.info("Edit Tour: " + this.neueTourName + " upload zu Postgres war: " + TourPlaner.editTour(this.selectedTour.getName(), newTour), "ViewModel - EditTour");
                            selectTour(this.neueTourName);
                        }
                    }
                }
                this.neueTourName = null;
                this.neueTourStart = null;
                this.neueTourZiel = null;
            }
        }
    }

    /**
     * Input mit duplications check des Namens und ohne content im input feld
     * @return false beim schließen
     */
    private boolean tourNameInput(){
        return tourNameInputDuplicatCheck("", true);
    }

    /**
     * Input des Tour Namen wird so lange durchgeführt, bis er korrekt ist. Das heißt:
     * er darf nicht schon vorhanden sein und er darf nicht Null sein
     * @param checkDuplicate Prüft ob name bereits vorhanden ist
     * @param content inhalt des Input fields
     * @return false beim schließen des input dialogs
     */
    private boolean tourNameInputDuplicatCheck(String content, boolean checkDuplicate){
        while(this.neueTourName == null) {
            this.neueTourName = AlertHelper.inputText(ConfigHelper.getLangIniString("tournametitle"),
                    ConfigHelper.getLangIniString("tournameheader"),
                    ConfigHelper.getLangIniString("tournamemsg"), content);
            System.out.println(this.neueTourName);
            if(this.neueTourName.isEmpty()){
                return false;
            }
            if (getTour(this.neueTourName) != null && checkDuplicate) {
                AlertHelper.warn(ConfigHelper.getLangIniString("achtung"),
                        ConfigHelper.getLangIniString("namevergebenheader"),
                        ConfigHelper.getLangIniString("namevergebenmsg1")
                                + this.neueTourName +
                                ConfigHelper.getLangIniString("namevergebenmsg2"));
                this.neueTourName = null;
            }
        }
        return true;
    }

    /**
     * Input der Startpunkt der Tour
     * Wird erst beendet wenn die eingabe erfolgreich war
     * @param content inhalt des Input fields
     * @return false beim schließen des input dialogs
     */
    private boolean tourStartInput(String content){
        while(this.neueTourStart == null){
            this.neueTourStart = AlertHelper.inputText(ConfigHelper.getLangIniString("startpunkttitle"),
                    ConfigHelper.getLangIniString("startpunktheader"),
                    ConfigHelper.getLangIniString("startpunktmsg"), content);
            if(this.neueTourStart.isEmpty()){
                return false;
            }
        }
        return true;
    }

    /**
     * Input des Zielpunktes der Tour
     * Wird erst beendet wenn die eingabe erfolgreich war
     * @param content inhalt des Input fields
     * @return false beim schließen des input dialogs
     */
    private boolean tourZielInput(String content){
        while(this.neueTourZiel == null){
            this.neueTourZiel = AlertHelper.inputText(ConfigHelper.getLangIniString("zielpunkttitle"),
                    ConfigHelper.getLangIniString("zielpunktheader"),
                    ConfigHelper.getLangIniString("zielpunktmsg"), content);
            if(this.neueTourZiel.isEmpty()){
                return false;
            }
        }
        return true;
    }


    /**
     * Fügt eine neue Tour hinzu
     */
    public boolean addTour(){
        if(tourNameInput()) {
            if(tourStartInput("")) {
                if (tourZielInput("")) {
                    if (getTour(this.neueTourName) == null) {
                        ProgressBar progressBar = new ProgressBar("Add Tour...");
                        Tour newTour = new Tour(this.neueTourName, "1", "1", 0, this.neueTourStart, this.neueTourZiel);
                        tourData.add(newTour);
                        tourNamen.add(this.neueTourName);
                        this.selectedTour = newTour;
                        progressBar.addProgress(10);
                        try {
                            TourPlaner.addTour(newTour, progressBar);
                        } catch (IOException e) {
                            LogHelper.error(e);
                            return false;
                        }
                    }
                }else return false;
            }else return false;
        }else return false;
        this.neueTourStart = null;
        this.neueTourZiel = null;
        this.neueTourName = null;
        return true;
    }

    private boolean keineTourSelected(){
        if (this.selectedTour == null){
            AlertHelper.warn(ConfigHelper.getLangIniString("achtung"),
                    ConfigHelper.getLangIniString("keinetourselected"),
                    ConfigHelper.getLangIniString("logtournotselectedmsg"));
            return true;
        }else {
            return false;
        }
    }

    /**
     * Bearbeitet einen gewählten Log eintrag
     */
    public void editLog(){
        System.out.println(this.selectedLog);
        if(this.selectedTour != null) {
            if(this.selectedLog != null) {
                AtomicReference<Tour> tourAkt = new AtomicReference<>();
                AtomicReference<Log> logAkt = new AtomicReference<>();
                this.tourData.forEach(tourd -> {
                    if (tourd.getName().equals(this.selectedTour.getName())) {
                        tourd.getLogs().forEach(log -> {
                            if (this.selectedLog.getId().equals(log.getId())) {
                                logAkt.set(log);
                                tourAkt.set(tourd);
                            }
                        });
                    }
                });
                double dauer = dauerInput(this.selectedLog.getDauer());
                double strecke = -1;
                double pause = -1.0;
                double rating = -1.0;
                double hightmeter = -1.0;
                boolean bemerkung = false;
                LocalDate datum = null;
                if (dauer >= 0) {
                    strecke = streckeInput(this.selectedLog.getStrecke() + "");
                    if (strecke >= 0) {
                        pause = pauseInput(this.selectedLog.getPause() + "");
                        if (pause >= 0.0) {
                            hightmeter = hightmeterInput(this.selectedLog.getHightmeter() + "");
                            if (hightmeter >= 0.0) {
                                bemerkung = bemerkungInput(this.selectedLog.getBemerkung());
                                if (bemerkung) {
                                    datum = dateInput(this.selectedLog.getDatum());
                                    if (datum != null) {
                                        rating = ratingInput(this.selectedLog.getRating());
                                        if(rating >= 0.0) {
                                            Log newLog = new Log(this.selectedLog.getId(), dauer, datum, strecke, this.neueBemerkung, hightmeter, pause, rating + "");
                                            this.logData.removeIf(ld -> ld.getId().equals(this.selectedLog.getId()));
                                            this.logData.add(newLog);
//                                tourAkt.get().delLog(this.selectedLog.getId());
//                                tourAkt.get().addLog(newLog);
                                            this.tourData.removeIf(td -> td.getName().equals(this.selectedTour.getName()));
                                            this.tourData.add(tourAkt.get());
                                            TourPlaner.editLog(this.selectedTour.getName(), newLog);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }else{
                //Kein log
                AlertHelper.warn(ConfigHelper.getLangIniString("achtung"), ConfigHelper.getLangIniString("keinlogselected"), ConfigHelper.getLangIniString("keinlogselectedmsg"));
            }
        }else {
            //Keine Tour
                AlertHelper.warn(ConfigHelper.getLangIniString("achtung"), ConfigHelper.getLangIniString("keinetourselected"), ConfigHelper.getLangIniString("deltournoselectmsg"));
        }
        this.neueBemerkung = null;
    }

    /**
     * Eingabe der Strecke bis diese Korrekt ist
     * @return Korrekte eingegebene Strecke
     */
    private double streckeInput(String content){
        return AlertHelper.inputNumber(ConfigHelper.getLangIniString("strecke"),
                ConfigHelper.getLangIniString("streckemsg"),
                ConfigHelper.getLangIniString("strecke") +
                        ConfigHelper.getLangIniString("doppelpunkt"), content);
    }

    /**
     * Eingabe der Dauer bis sie korrekt ist
     * @param content Wenn der Content -1.0 ist, wird er nicht in das input feld eingeschleust, sonst schon
     * @return Die eingegebene Dauer
     */
    private double dauerInput(double content){
        String realcontent = "";
        if (content != -1){
            realcontent = content + "";
        }
        return AlertHelper.inputNumber(ConfigHelper.getLangIniString("dauer"),
                ConfigHelper.getLangIniString("dauermsg"),
                ConfigHelper.getLangIniString("dauer") +
                        ConfigHelper.getLangIniString("doppelpunkt"), realcontent);
    }

    /**
     * Eingabe des Ratings bis sie korrekt ist
     * @param content Wenn der Content -1.0 ist, wird er nicht in das input feld eingeschleust, sonst schon
     * @return Die eingegebene des Ratings
     */
    private double ratingInput(String content){
        String realcontent = "";
        if (!content.isEmpty()){
            realcontent = content + "";
        }
        double input = -1;
        while(input <= 0) {
            input = AlertHelper.inputNumber(ConfigHelper.getLangIniString("bewertung"),
                    ConfigHelper.getLangIniString("bewertungmsg"),
                    ConfigHelper.getLangIniString("bewertung") +
                            ConfigHelper.getLangIniString("doppelpunkt"), realcontent);
            if(input < 0 && input > 5){
                input = -1;
                AlertHelper.warn(ConfigHelper.getLangIniString("achtung"), ConfigHelper.getLangIniString("bewertungeror"), ConfigHelper.getLangIniString("bewertungeror2"));
            }
        }
        return input;
    }

    /**
     * Datum eingabe bis diese Korrekt ist
     * @return Das eingegebene korekte Datum
     */
    private LocalDate dateInput(LocalDate date){
        LocalDate neuesDatum = null;
        neuesDatum = AlertHelper.datePicker(date);
        System.out.println(neuesDatum);
        return neuesDatum;
    }

    /**
     * Bemerkung input, darf auch leer bleiben
     * @param content Startwert im Input feld
     * @return True bei erfolg, wenn es abgebrochen wird ist es false
     */
    private boolean bemerkungInput(String content){
        this.neueBemerkung = AlertHelper.inputTextNotNull(ConfigHelper.getLangIniString("bemerkung"),
                ConfigHelper.getLangIniString("bemerkungheader"),
                ConfigHelper.getLangIniString("bemerkung")+
                        ConfigHelper.getLangIniString("doppelpunkt"), content, false);
        return !(this.neueBemerkung == null);
    }

    private double hightmeterInput(String content) {
        return AlertHelper.inputNumber(ConfigHelper.getLangIniString("hightmeter"),
                ConfigHelper.getLangIniString("hightmetermsg"),
                ConfigHelper.getLangIniString("hightmeter") +
                        ConfigHelper.getLangIniString("doppelpunkt"), content);
    }

    /**
     * Pause eingabe bis diese Korrekt ist
     * @param content Default value für das Input Feld
     * @return -1 bei error
     */
    private double pauseInput(String content){
        return AlertHelper.inputNumber(ConfigHelper.getLangIniString("pause"),
                ConfigHelper.getLangIniString("pausemsg"),
                ConfigHelper.getLangIniString("pause") +
                        ConfigHelper.getLangIniString("doppelpunkt"), content);
    }

    /**
     * Fügt einen Log eintrag zu einer Tour hinzu.
     * Ist keine Tour ausgewählt, dann kommt eine Warnung an den User!
     */
    public void addLog(){
        if (!keineTourSelected()){
            this.tourData.forEach(s -> {
                if (s.getName().equals(this.selectedTour.getName())) {
                    AtomicReference<String> newId = new AtomicReference<>();
                    newId.set(UUID.randomUUID().toString());
                    this.logData.forEach(ss -> {
                        if (ss.getId().equals(newId.get())) {
                            newId.set(UUID.randomUUID().toString());
                        }
                    });
                    double dauer = dauerInput(-1.0);
                    double strecke, pause, hightmeter;
                    boolean bemerkung = false;
                    double rating = -1.0;
                    LocalDate date = null;
                    if(dauer >= 0) {
                        strecke = streckeInput("");
                        if (strecke >= 0.0) {
                            pause = pauseInput("");
                            if(pause >= 0.0) {
                                hightmeter = hightmeterInput("");
                                if (hightmeter >= 0.0) {
                                     bemerkung = bemerkungInput("");
                                     if (bemerkung){
                                        date = dateInput(LocalDate.now());
                                        if (date != null) {
                                            rating = ratingInput("");
                                            if(rating >= 0.0) {
                                                Log newLog = new Log(newId.get(), dauer, date, strecke, this.neueBemerkung, hightmeter, pause, rating + "");
                                                this.logData.add(newLog);
                                                TourPlaner.addLog(this.selectedTour.getName(), newLog);
                                                s.addLog(newLog);
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            });
            this.neueBemerkung = null;
        }
    }




    /**
     * Entfernt ein Log anhand des selectierten Logs
     */
    public void delLog(){
        if(this.selectedLog != null) {
                TourPlaner.delLog(this.selectedTour.getName(), this.selectedLog.getId());
                this.selectedLog = null;
        }else{
            AlertHelper.warn(ConfigHelper.getLangIniString("achtung"),
                    ConfigHelper.getLangIniString("keinetourselected"),
                    ConfigHelper.getLangIniString("deltournoselectmsg"));
        }
    }

    /**
     * Selectiert ein Log anhand der Id
     * @param id Id welche zu selectieren ist
     */
    public void selectLog(String id){
        this.selectedLog = getLog(id);
    }

    /**
     * Holt das selectierte Log als Log Objekt
     * @return Das selectierte Log Objekt
     */
    public Log getSelectedLog(){
        return this.selectedLog;
    }

    /**
     * Holt ein Log anhand seiner ID
     * @param id Id des Log Eintrags
     * @return Das gefundene Log
     */
    public Log getLog(String id){
        AtomicReference<Log> returnLog = new AtomicReference<>();
        this.logData.forEach(s -> {
            if(s.getId().equals(id)){
                returnLog.set(s);
            }
        });
        return returnLog.get();
    }

    public ObservableList<Log> getLogData() {
        logData.removeIf(s -> true);
        if(this.selectedTour != null) logData.addAll(TourPlaner.getLogs(this.selectedTour.getName()));
        return logData;
    }

    public void setSelectedTour(Tour selectedTour) {
        this.selectedTour = selectedTour;
    }

    /**
     * Holt das Tourobjekt anhand des Namens
     * @param tourname Name der Tour
     * @return Gefundene Tour
     */
    public Tour getTour(String tourname){
        AtomicReference<Tour> returnTour = new AtomicReference<>();
        tourData.forEach(s -> {
            if(s.getName().equals(tourname)){
                returnTour.set(s);
            }
        });
        return returnTour.get();
    }

    /**
     * Selectiert eine Tour anhand des eindeutigen Namens
     * @param tourname Der Name der Tour
     */
    public void selectTour(String tourname){
        this.selectedTour = getTour(tourname);
        this.logData.removeIf(s -> true); // Löscht alles aus dem Array
        this.logData.addAll(this.selectedTour.getLogs());
    }

//    /**
//     * Selectiert eine Tour anhand des Tour Objects.
//     * Kann z.b. verwerndet werden um das selectierte Tour Object zu bearbeiten
//     * @param selected
//     */
//    public void setSelectedTour(Tour selected){
//        this.selectedTour = selected;
//    }

    public Tour getSelectedTour() {
        return selectedTour;
    }

    public String getNeueTourZiel() {
        return neueTourZiel;
    }

    public void setNeueTourZiel(String neueTourZiel) {
        this.neueTourZiel = neueTourZiel;
    }

    public ObservableList<String> getTourNamen() {
        ArrayList<String> namen = new ArrayList<>();
        getTourData().forEach(s -> {
            namen.add(s.getName());
        });
        tourNamen.clear();
        tourNamen.addAll(namen);
        return tourNamen;
    }

    public String getNeueTourName() {
        return neueTourName;
    }

    public void setNeueTourName(String neueTourName) {
        this.neueTourName = neueTourName;
    }

    public String getNeueTourStart() {
        return neueTourStart;
    }

    public void setNeueTourStart(String neueTourStart) {
        this.neueTourStart = neueTourStart;
    }

    public ObservableList<Tour> getTourData() {
        tourData.clear();
        ArrayList<Tour> touren = TourPlaner.getAllTours();
        if(touren != null) {
            tourData.addAll(touren);
        }
        return tourData;
    }

    /**
     * Entfernt eine Tour anhand der ausgewählten Tour
     */
    public void delTour(){
        if (this.selectedTour != null) {
            String tourname = this.selectedTour.getName();
            try {
                tourData.removeIf(s -> s.getName().equals(tourname));
                tourNamen.removeIf(s -> s.equals(tourname));
                logData.removeIf(s -> true);
                TourPlaner.delTour(tourname);
                setSelectedTour(null);
            } catch (NullPointerException e) {
                LogHelper.error(e);
            }
        }else {
            AlertHelper.warn(ConfigHelper.getLangIniString("achtung"),
                    ConfigHelper.getLangIniString("keinetourselected"),
                    ConfigHelper.getLangIniString("deltournoselectmsg"));
        }
    }

    /**
     * Sucht eine Tour
     * @param sucheInput Text nach dem gesucht werden soll
     */
    public void suche(String sucheInput){
        if(sucheInput.isEmpty()){
            AlertHelper.warn(ConfigHelper.getLangIniString("achtung"),
                    ConfigHelper.getLangIniString("suchfeldleer"),
                    ConfigHelper.getLangIniString("suchtextzuerst"));
        }
         ArrayList<Tour> result = TourPlaner.sucheTour(sucheInput);
        tourNamen.clear();
        for (Tour tour:result) {
            tourNamen.add(tour.getName());
        }
        this.sucheAktiv = true;
    }

    public boolean isSucheAktiv() {
        return sucheAktiv;
    }

    public void setSucheAktiv(boolean sucheAktiv) {
        this.sucheAktiv = sucheAktiv;
    }

    /**
     * Öffnet github im standart browser
     *
     */
    public void gitWebBrowser(){
        openBrowser(ConfigHelper.getIniString(ConfigHelper.getStandartConfig(), "hilfe", "gitrepo"), "Git");
    }

    /**
     * Öffnet Java Doc im standart browser
     *
     */
    public void javaDocBrowser(){
        openBrowser(ConfigHelper.getIniString(ConfigHelper.getStandartConfig(), "hilfe", "javadoc"), "JavaDoc");
    }

    /**
     * Öffnet Doxygen Doc im standart browser
     *
     */
    public void doxygenDocBrowser(){
        openBrowser(ConfigHelper.getIniString(ConfigHelper.getStandartConfig(), "hilfe", "doxygendoc"), "Doxygen");
    }

    /**
     * Öffnet einen link im Standart Browser
     * @param uriString
     * @param appname
     */
    private void openBrowser(String uriString, String appname){
        Desktop desktop = java.awt.Desktop.getDesktop();
        try {
            URI oURL = new URI(
                    uriString);
            desktop.browse(oURL);
        } catch (URISyntaxException | IOException e) {
            LogHelper.error(e.getMessage(), ConfigHelper.getLangIniString("openbrowsererror") + appname);
            AlertHelper.exerror(ConfigHelper.getLangIniString("browserexception"),
                    ConfigHelper.getLangIniString("openbrowsererror") + appname,
                    ConfigHelper.getLangIniString("browserexceptionmsg"), e);
        }
    }

    /**
     * Funktion für noch nicht implementierte sachen wie im Menu der 'Bearbeiten' und 'Optionen' Knopf
     */
    public void nimpButton(){
        AlertHelper.inform(ConfigHelper.getLangIniString("achtung"),
                ConfigHelper.getLangIniString("fktnichtimplementiert"),
                ConfigHelper.getLangIniString("vergessenodernochnichtsoweit"));
    }

    /**
     * Startet den Tour Report
     */
    public void tourReport() {
        if (this.selectedTour == null){
            AlertHelper.warn(ConfigHelper.getLangIniString("achtung"),
                    ConfigHelper.getLangIniString("keinetourselected"),
                    ConfigHelper.getLangIniString("logtournotselectedmsg"));
        }else {
           TourPlaner.doReport(this.selectedTour.getName());
        }
    }

    /**
     * Exportiert alle daten in ein File das hier gewählt wird
     */
    public void exportData(){
        String file = AlertHelper.fileChooser("Exportiere");
        if(file != null){
            try {
                new Exporter(file).doExport();
            } catch (IOException e) {
                AlertHelper.error("Error", "Export Error", "Fehler beim Exportieren");
                LogHelper.error(e);
            }
        }
    }

    /**
     * Importiert alle daten von einem File das hier gewählt wird
     */
    public void importData() {
        String file = AlertHelper.fileChooser("Importiere");
        if (file != null){
            this.tourNamen = FXCollections.observableArrayList();
            new Exporter(file).doImport();
        }
    }

    /**
     * Erstellt einen Summary Report
     */
    public void sumReport(){
        Reporter.sumReport();
    }
}
