/*
 * Copyright 2015-2016 Todd Kulesza <[email protected]>.
 *
 * This file is part of Archivo.
 *
 * Archivo 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.
 *
 * Archivo 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 Archivo.  If not, see <http://www.gnu.org/licenses/>.
 */

package net.straylightlabs.archivo.view;

import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Pane;
import net.straylightlabs.archivo.Archivo;
import net.straylightlabs.archivo.model.ArchiveStatus;
import net.straylightlabs.archivo.model.Recording;
import org.controlsfx.glyphfont.FontAwesome;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.awt.*;
import java.io.IOException;
import java.net.URL;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.Period;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.stream.Collectors;

public class RecordingDetailsController implements Initializable {
    private final Archivo mainApp;
    private final RecordingSelection recordingSelection;
    private final Map<URL, Image> imageCache;
    private final BooleanProperty showPoster;
    private final StringProperty expectedRemovalText;

    @FXML
    private Label title;
    @FXML
    private Label subtitle;
    @FXML
    private Label episode;
    @FXML
    private Label originalAirDate;
    @FXML
    private Label date;
    @FXML
    private Label channel;
    @FXML
    private Label duration;
    @FXML
    private Label description;
    @FXML
    private Label copyProtected;
    @FXML
    private Label stillRecording;
    @FXML
    private Label expectedDeletion;
    @FXML
    private ImageView poster;
    @FXML
    private Pane posterPane;
    @FXML
    private Button archiveButton;
    @FXML
    private Button cancelButton;
    @FXML
    private Button playButton;

    public final static Logger logger = LoggerFactory.getLogger(RecordingDetailsController.class);

    public RecordingDetailsController(Archivo mainApp) {
        this.mainApp = mainApp;
        this.recordingSelection = mainApp.getRecordingListController().getRecordingSelection();
        imageCache = new HashMap<>();
        showPoster = new SimpleBooleanProperty(false);
        expectedRemovalText = new SimpleStringProperty();
    }

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        // Only set the preferred height; the width will scale appropriately
        poster.setFitHeight(Recording.DESIRED_IMAGE_HEIGHT);

        setupIcons();
        setupLabelBindings();
        setupControlBindings();
        recordingSelection.posterUrlProperty().addListener((observable, oldValue, newValue) -> {
            setPosterFromURL(newValue);
        });
        recordingSelection.expectedRemovalDateProperty().addListener(((observable, oldValue, newValue) -> {
            updateExpectedDeletion(newValue);
        }));
    }

    private void setupIcons() {
        archiveButton.setGraphic(mainApp.getGlyph(FontAwesome.Glyph.DOWNLOAD));
        cancelButton.setGraphic(mainApp.getGlyph(FontAwesome.Glyph.CLOSE));
        playButton.setGraphic(mainApp.getGlyph(FontAwesome.Glyph.PLAY));
    }

    private void setupLabelBindings() {
        title.textProperty().bind(recordingSelection.showTitleProperty());
        title.visibleProperty().bind(recordingSelection.showTitleProperty().isEmpty().not());
        title.managedProperty().bind(recordingSelection.showTitleProperty().isEmpty().not());

        subtitle.textProperty().bind(recordingSelection.episodeTitleProperty());
        subtitle.visibleProperty().bind(recordingSelection.episodeTitleProperty().isEmpty().not());
        subtitle.managedProperty().bind(recordingSelection.episodeTitleProperty().isEmpty().not());

        description.textProperty().bind(recordingSelection.descriptionProperty());
        description.visibleProperty().bind(recordingSelection.descriptionProperty().isEmpty().not());
        description.managedProperty().bind(recordingSelection.descriptionProperty().isEmpty().not());

        episode.textProperty().bind(recordingSelection.episodeNumberProperty());
        episode.visibleProperty().bind(recordingSelection.episodeNumberProperty().isEmpty().not());
        episode.managedProperty().bind(recordingSelection.episodeNumberProperty().isEmpty().not());

        date.textProperty().bind(recordingSelection.dateRecordedProperty());
        date.visibleProperty().bind(recordingSelection.dateRecordedProperty().isEmpty().not());
        date.managedProperty().bind(recordingSelection.dateRecordedProperty().isEmpty().not());

        originalAirDate.textProperty().bind(recordingSelection.dateFirstAiredProperty());
        originalAirDate.visibleProperty().bind(recordingSelection.dateFirstAiredProperty().isEmpty().not());
        originalAirDate.managedProperty().bind(recordingSelection.dateFirstAiredProperty().isEmpty().not());

        duration.textProperty().bind(recordingSelection.durationProperty());
        duration.visibleProperty().bind(recordingSelection.durationProperty().isEmpty().not());
        duration.managedProperty().bind(recordingSelection.durationProperty().isEmpty().not());

        channel.textProperty().bind(recordingSelection.channelProperty());
        channel.visibleProperty().bind(recordingSelection.channelProperty().isEmpty().not());
        channel.managedProperty().bind(recordingSelection.channelProperty().isEmpty().not());

        expectedDeletion.textProperty().bind(expectedRemovalText);
        expectedDeletion.visibleProperty().bind(recordingSelection.expectedRemovalDateProperty().isNotNull());
        expectedDeletion.managedProperty().bind(recordingSelection.expectedRemovalDateProperty().isNotNull());

        copyProtected.visibleProperty().bind(recordingSelection.isCopyProtectedProperty());
        copyProtected.managedProperty().bind(recordingSelection.isCopyProtectedProperty());

        stillRecording.visibleProperty().bind(recordingSelection.isRecordingProperty());
        stillRecording.visibleProperty().bind(recordingSelection.isRecordingProperty());

        posterPane.visibleProperty().bind(showPoster);
        posterPane.managedProperty().bind(showPoster);
    }

    private void setupControlBindings() {
        archiveButton.visibleProperty().bind(
                Bindings.and(recordingSelection.showControlsProperty(),
                        Bindings.and(cancelButton.visibleProperty().not(), playButton.visibleProperty().not())
                )
        );
        archiveButton.managedProperty().bind(
                Bindings.and(recordingSelection.showControlsProperty(),
                        Bindings.and(cancelButton.visibleProperty().not(), playButton.visibleProperty().not())
                )
        );
        archiveButton.disableProperty().bind(recordingSelection.isArchivableProperty().not());
        cancelButton.visibleProperty().bind(recordingSelection.isCancellableProperty());
        cancelButton.managedProperty().bind(recordingSelection.isCancellableProperty());
        playButton.visibleProperty().bind(recordingSelection.isPlayableProperty());
        playButton.managedProperty().bind(recordingSelection.isPlayableProperty());
    }

    @FXML
    public void archive(ActionEvent event) {
        mainApp.archiveSelection();
    }

    @FXML
    public void cancel(ActionEvent event) {
        for (Recording recording : recordingSelection.getRecordings()) {
            logger.info("Cancel archiving of recording {}...", recording.getFullTitle());
            mainApp.cancelArchiving(recording);
            recording.setStatus(ArchiveStatus.EMPTY);
        }
    }

    @FXML
    public void play(ActionEvent event) {
        for (Recording recording : recordingSelection.getRecordings()) {
            logger.info("Playing recording {}...", recording.getDestination());
            try {
                Desktop.getDesktop().open(recording.getDestination().toFile());
            } catch (IOException e) {
                logger.error("Error playing '{}': ", recording.getDestination(), e);
            }
        }
    }

    @FXML
    public void openFolder(ActionEvent event) {
        for (Recording recording : recordingSelection.getRecordings()) {
            logger.info("Opening folder containing recording {}...", recording.getDestination());
            try {
                Desktop.getDesktop().browse(recording.getDestination().getParent().toUri());
            } catch (IOException e) {
                logger.error("Error playing '{}': ", recording.getDestination(), e);
            }
        }
    }

    @FXML
    public void delete(ActionEvent event) {
        List<Recording> toDelete = recordingSelection.getRecordingsWithChildren().stream().filter(
                recording -> !recording.isSeriesHeading()).collect(Collectors.toList()
        );
        mainApp.deleteFromTivo(toDelete);
    }

    private void updateExpectedDeletion(LocalDateTime expectedRemovalDate) {
        expectedDeletion.getStyleClass().clear();
        expectedRemovalText.setValue("");

        if (expectedRemovalDate == null) {
            return;
        }

        LocalDate today = LocalDate.now();
        Period timeUntilDeletion = today.until(expectedRemovalDate.toLocalDate());
        int daysUntilDeletion = timeUntilDeletion.getDays();
        if (daysUntilDeletion < 1) {
            expectedRemovalText.setValue("Will be removed today");
            expectedDeletion.getStyleClass().add("removing-today");
        } else if (daysUntilDeletion < 2) {
            expectedRemovalText.setValue("Will be removed tomorrow");
            expectedDeletion.getStyleClass().add("removing-today");
        } else if (daysUntilDeletion < 7) {
            expectedRemovalText.setValue(String.format("Will be removed in %d days", daysUntilDeletion));
            expectedDeletion.getStyleClass().add("removing-soon");
        }
    }

    private void setPosterFromURL(URL url) {
        if (url != null) {
            Image posterImage = loadImageFromURL(url);
            if (posterImage != null) {
                setPosterFromImage(posterImage);
            }
        } else {
            setPosterFromImage(null);
        }
    }

    private Image loadImageFromURL(URL url) {
        Image cachedImage = imageCache.get(url);
        if (cachedImage == null) {
            ImageDownloadTask downloadTask = new ImageDownloadTask(url);
            // Download and cache the requested image
            downloadTask.setOnSucceeded(event -> {
                Image image = (Image) event.getSource().getValue();
                if (image == null) {
                    throw new NullPointerException();
                }
                setPosterFromImage(image);
                imageCache.put(url, image);
            });
            mainApp.getRpcExecutor().submit(downloadTask);
        }
        return cachedImage;
    }

    private void setPosterFromImage(Image image) {
        if (image != null) {
            poster.setImage(image);
            showPoster.setValue(true);
        } else {
            showPoster.setValue(false);
        }
    }
}