/*
 * Copyright (C) 2017 John Garner <[email protected]>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.pikatimer.timing.reader;

import com.pikatimer.PikaPreferences;
import com.pikatimer.timing.TimingListener;
import com.pikatimer.timing.TimingReader;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.concurrent.Semaphore;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Stream;
import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Task;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.stage.FileChooser;
import org.apache.commons.io.input.Tailer;
import org.apache.commons.io.input.TailerListenerAdapter;
import org.controlsfx.control.ToggleSwitch;

/**
 *
 * @author John Garner <[email protected]>
 */
public abstract class TailingReader implements TimingReader{
        
    protected TimingListener timingListener;
    protected File sourceFile; 
    protected final StringProperty fileName; 
    private Pane displayPane; 
    private Button inputButton;
    protected TextField inputTextField; 
    protected Label statusLabel; 
    private HBox displayHBox; 
    private VBox displayVBox; 
    protected Tailer tailer;
    private Thread tailingThread; 
    private Thread readingThread;
    protected final BooleanProperty readingStatus;
    ProgressIndicator watchProgressIndicator;
    ToggleSwitch autoImportToggleSwitch;
    private Semaphore reading = new Semaphore(1);
;
    
    public TailingReader(){
        fileName = new SimpleStringProperty();
        readingStatus = new SimpleBooleanProperty();
        
    }



    public void selectInput() {
        final FileChooser fileChooser = new FileChooser();
        fileChooser.setTitle("Select File");
        
        if (sourceFile != null && sourceFile.exists()) {
            fileChooser.setInitialDirectory(sourceFile.getParentFile()); 
            fileChooser.setInitialFileName(sourceFile.getName());
        } else {
            fileChooser.setInitialDirectory(PikaPreferences.getInstance().getCWD()); 
        }
        fileChooser.getExtensionFilters().addAll(
                new FileChooser.ExtensionFilter("Text Files", "*.txt"),
                new FileChooser.ExtensionFilter("All files", "*")
            );
        
        sourceFile = fileChooser.showOpenDialog(inputButton.getScene().getWindow());
        if (sourceFile != null) {
            // if we are auto-importing, stop that
            readingStatus.set(false);
            
            fileName.setValue(sourceFile.getAbsolutePath());
            // save the filename 
            timingListener.setAttribute("TailingReader:filename", sourceFile.getAbsolutePath());
            
            // set the text field to the filename
            inputTextField.textProperty().setValue(fileName.getValueSafe());
            // read the file
            if (!sourceFile.canRead()){
                statusLabel.setText("Unable to open file: " + fileName.getValueSafe());
            } else readOnce();
        }                
    }

    

    @Override
    public void startReading() {
        System.out.println("TailingReader:StartReading() called");
        if (tailingThread != null && tailingThread.isAlive()) return;
        if (readingThread != null && readingThread.isAlive()) return;
        Task readingTask = new Task<Void>() {
                @Override public Void call() {
                    try {
                        // make sure the file exists

                        
                        while (readingStatus.getValue() && (sourceFile == null || !sourceFile.exists() || !sourceFile.canRead() || !sourceFile.isFile())){
                            Thread.sleep(1000);
                            System.out.println("Waiting for " + sourceFile.getPath());
                            Platform.runLater(() ->{
                                statusLabel.setText("Waiting for " + sourceFile.getPath());
                            });
                        }   
                        if (readingStatus.getValue() ) {
                            Platform.runLater(() -> statusLabel.setText("Reading file: " + sourceFile.getPath()));

                            MyHandler listener = new MyHandler();
                            tailer = new Tailer(sourceFile, listener, 1000, Boolean.FALSE, Boolean.TRUE);
                            tailingThread = new Thread(tailer);
                            tailingThread.setDaemon(true); // optional
                            tailingThread.start();
                            readingStatus.setValue(Boolean.TRUE);
                        }
                    } catch (InterruptedException ex) {
                        Logger.getLogger(TailingReader.class.getName()).log(Level.SEVERE, null, ex);
                    }
                    return null;
                }
        };
        readingThread = new Thread(readingTask);
        readingThread.setDaemon(true); // optional
        readingThread.start();
    }
    
    @Override
    public void stopReading() {
        if (tailer != null) {
            tailer.stop();
        }
        readingStatus.setValue(Boolean.FALSE);
    }


    @Override
    public void showControls(Pane p) {
        
        if (displayPane == null) {
            // initialize our display
            displayHBox = new HBox();
            displayVBox = new VBox();
            watchProgressIndicator = new ProgressIndicator();
            autoImportToggleSwitch = new ToggleSwitch("Auto-Import File");
            autoImportToggleSwitch.selectedProperty().set(false);
            autoImportToggleSwitch.setPadding(new Insets(3, 0, 0, 0)); // this is a hack to get around a ToggleSwitch bug
            //autoImportToggleSwitch.setMaxWidth(75);
            statusLabel = new Label("");
            inputButton = new Button("Select File...");
            inputTextField = new TextField();
            displayVBox.setSpacing(5); 
            //displayVBox.setPadding(new Insets(5, 5, 5, 5));
            
            
            inputTextField.focusedProperty().addListener((ObservableValue<? extends Boolean> arg0, Boolean oldPropertyValue, Boolean newPropertyValue) -> {
                if (!newPropertyValue && !fileName.getValueSafe().equals(inputTextField.textProperty().getValueSafe())) {
                    // if we are auto-importing, stop that
                    stopReading();
                    
                    sourceFile = new File(inputTextField.textProperty().getValueSafe()).getAbsoluteFile();
                    fileName.setValue(sourceFile.getAbsolutePath());

                    
                    // save the filename 
                    timingListener.setAttribute("TailingReader:filename", inputTextField.textProperty().getValueSafe());

                    // read the file
                    if (!sourceFile.canRead()){
                        statusLabel.setText("Unable to open file: " + fileName.getValueSafe());
                    } else readOnce();
                        
                } else {
                    System.out.println("No change in file name");
                }
            });
            
            displayHBox.setSpacing(5);
            displayHBox.setAlignment(Pos.CENTER_LEFT);
            displayHBox.getChildren().addAll(inputTextField, inputButton, autoImportToggleSwitch, watchProgressIndicator); 
            displayVBox.getChildren().addAll(displayHBox, statusLabel); 
            
            // Set the action for the inputButton
            inputButton.setOnAction((event) -> {
                // Button was clicked, do something...
                selectInput();
            });
            
            watchProgressIndicator.visibleProperty().bind(autoImportToggleSwitch.selectedProperty());
            watchProgressIndicator.setProgress(-1.0);
            // get the current status of the reader
            //watchProgressIndicator.setPrefHeight(30.0);
            watchProgressIndicator.setMaxHeight(30.0);
            autoImportToggleSwitch.selectedProperty().addListener((ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> {
                if(newValue) {
                    System.out.println("TailingReader: autoImportToggleSwitch event: calling startReading()");
                    startReading();
                } else {
                    System.out.println("TailingReader: autoImportToggleSwitch event: calling stopReading()");
                    stopReading();
                }
            });
            autoImportToggleSwitch.selectedProperty().bindBidirectional(readingStatus);
            
            inputTextField.textProperty().setValue(fileName.getValueSafe());
            // set the action for the inputTextField
            
        }
        
        // If we were previously visible, clear the old one
        if (displayPane != null) displayPane.getChildren().clear();
        
        // Now show ourselves.... 
        displayPane = p; 
        displayPane.getChildren().clear();
        displayPane.getChildren().add(displayVBox); 
        
        
    }

    @Override
    public BooleanProperty getReadingStatus() {
        return readingStatus; 
    }
    
    private class MyHandler extends TailerListenerAdapter {
        @Override
        public void handle(String line) {
            System.out.println("handle: " + line);
            process(line);
        }
    }
    
    
    public abstract void process(String s);
        
    @Override
    public void readOnce() {
        // get the event date, just in case we need it
        System.out.println("TailingReader.readOnce called.");
        stopReading();
        
        if (sourceFile == null || !sourceFile.exists() || !sourceFile.canRead() || !sourceFile.isFile()) {
            statusLabel.setText("Unable to open file: " + fileName.getValueSafe());
            return;
        }
        System.out.println("  Current file is: \"" + sourceFile.getAbsolutePath() + "\"");
        // Run this in a tailingThread....
        Task task;
        task = new Task<Void>() {
            @Override public Void call() {
                try {
                    reading.acquire();
                    try (Stream<String> s = Files.lines(sourceFile.toPath())) {
                        s.map(line -> line.trim()).filter(line -> !line.isEmpty()).forEach(line -> {
                            //System.out.println("readOnce read " + s);
                            process(line);
                        });
                        s.close();
                    } catch (Exception ex){
                        ex.printStackTrace();
                    }
                    
                } catch (Exception ex){
                    ex.printStackTrace();
                }
                reading.release();
                return null;
            }
        };
        new Thread(task).start();
        
    }
    
    

    @Override
    public void setTimingListener(TimingListener t) {
        timingListener = t; 
        
        // get any existing attributes
        String filename = timingListener.getAttribute("TailingReader:filename");
        if (filename != null) {
            System.out.println("TailingReader: Found existing file setting: " + filename);
            sourceFile = new File(filename).getAbsoluteFile();
            fileName.setValue(filename);
            
        } else {
            System.out.println("TailingReader: Did not find existing file setting." );
        }
        
        
    }
    
//    @Override
//    public Boolean chipIsBib() {
//        return Boolean.FALSE; 
//    }

}