package com.ecep.contract.task;

import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.ecep.contract.Desktop;
import com.ecep.contract.Message;
import com.ecep.contract.TaskStatus;

import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.concurrent.Worker;
import javafx.concurrent.WorkerStateEvent;
import javafx.event.EventHandler;
import lombok.Getter;
import lombok.Setter;

/**
 * 被监控的任务包装类
 */
public class MonitoredTask<T> implements Runnable {
    private static final Logger logger = LoggerFactory.getLogger(MonitoredTask.class);
    @Getter
    private final Tasker<T> delegate;
    @Getter
    private final String taskId;
    @Getter
    private final long startTime;
    @Setter
    private Long timeoutSeconds;
    private ScheduledFuture<?> timeoutExecutor;
    private final SimpleStringProperty title = new SimpleStringProperty("-");
    private final SimpleStringProperty message = new SimpleStringProperty("");
    private final ObservableList<Message> messages = FXCollections.observableArrayList();
    private final SimpleObjectProperty<TaskStatus> state = new SimpleObjectProperty<>();
    private final ObjectProperty<Throwable> exception = new SimpleObjectProperty<>(this, "exception");
    private final ObjectProperty<Runnable> succeededProperty = new SimpleObjectProperty<>();
    private final ObjectProperty<Runnable> failedProperty = new SimpleObjectProperty<>();
    private final ObjectProperty<Runnable> cancelledHandler = new SimpleObjectProperty<>();
    private final AtomicBoolean timeoutHandled = new AtomicBoolean(false);

    public MonitoredTask(Tasker<T> delegate, String taskId) {
        this.delegate = delegate;
        this.taskId = taskId;
        this.startTime = System.currentTimeMillis();

        // 绑定属性
        title.set(delegate.getTitle());
        delegate.titleProperty().addListener(this::titleChanged);
        message.set(delegate.getMessage());
        delegate.messageProperty().addListener(this::messageChanged);
        delegate.stateProperty().addListener(this::stateChanged);
        delegate.setMessageHandler(this::messageHandler);
        delegate.totalWorkProperty();
    }

    private boolean messageHandler(Message message) {
        messages.add(message);
        // return false allow the messageProperty to update
        return false;
    }

    public ObservableList<Message> getMessages() {
        return messages;
    }

    private void titleChanged(ObservableValue<? extends String> observable, String oldValue, String newValue) {
        titleProperty().set(newValue);
    }

    private void messageChanged(ObservableValue<? extends String> observable, String oldValue, String newValue) {
        messageProperty().set(newValue);
    }

    private void stateChanged(ObservableValue<? extends Worker.State> observable, Worker.State oldValue,
            Worker.State newValue) {
        switch (newValue) {
            case RUNNING: {
                if (stateProperty().get() != TaskStatus.RUNNING) {
                    if (logger.isWarnEnabled()) {
                        logger.warn("任务{} 的 delegate 状态从 {} 变更为 {} 时, monitor的状态 {} 不是 RUNNING", taskId, oldValue,
                                newValue, stateProperty().get());
                    }
                }
                stateProperty().set(TaskStatus.RUNNING);
                break;
            }
            case CANCELLED:
                stateProperty().set(TaskStatus.CANCELLED);
                cancelledProperty().get().run();
                break;
            default:
                break;
        }
    }

    @Override
    public void run() {
        if (logger.isDebugEnabled()) {
            logger.debug("任务 {} 开始执行", taskId);
        }
        stateProperty().set(TaskStatus.RUNNING);
        try {
            call();
            if (logger.isDebugEnabled()) {
                logger.debug("任务 {} 执行完成", taskId);
            }
        } catch (Throwable e) {
            exceptionProperty().set(e);
            if (logger.isDebugEnabled()) {
                logger.debug("任务 {} 执行异常", taskId, e);
            }
        } finally {
            try {
                if (exceptionProperty().get() == null) {
                    stateProperty().set(TaskStatus.SUCCEEDED);
                    succeededProperty().get().run();
                } else {
                    stateProperty().set(TaskStatus.FAILED);
                    failedProperty().get().run();
                }
            } catch (Exception e) {
                if (logger.isDebugEnabled()) {
                    logger.debug("任务 {} 回调执行异常", taskId, e);
                }
            }
        }

    }

    public StringProperty titleProperty() {
        return title;
    }

    public StringProperty messageProperty() {
        return message;
    }

    public ReadOnlyDoubleProperty progressProperty() {
        return delegate.progressProperty();
    }

    public ReadOnlyDoubleProperty totalWorkProperty() {
        return delegate.totalWorkProperty();
    }

    public ObjectProperty<TaskStatus> stateProperty() {
        return state;
    }

    public ObjectProperty<Throwable> exceptionProperty() {
        return exception;
    }

    public ObjectProperty<Runnable> succeededProperty() {
        return succeededProperty;
    }

    public ObjectProperty<Runnable> failedProperty() {
        return failedProperty;
    }

    public ObjectProperty<Runnable> cancelledProperty() {
        return cancelledHandler;
    }

    protected T call() throws Exception {
        // 设置超时监控
        if (timeoutSeconds != null) {
            setupTimeoutMonitor();
        }

        try {
            delegate.run();
            return delegate.get();
        } finally {
            // 取消超时监控
            if (timeoutExecutor != null) {
                timeoutExecutor.cancel(true);
            }
        }
    }

    /**
     * 设置超时监控
     */
    private void setupTimeoutMonitor() {
        timeoutExecutor = getExecutorService().schedule(() -> {
            if (isRunning() && timeoutHandled.compareAndSet(false, true)) {
                updateMessage("任务执行超时，正在取消...");
                stateProperty().set(TaskStatus.TIMED_OUT);
                delegate.cancel();
            }
        }, timeoutSeconds, TimeUnit.SECONDS);
    }

    public void submit() {
        getExecutorService().submit(this);
    }

    public ScheduledFuture<?> schedule(long delay, TimeUnit unit) {
        ScheduledFuture<?> schedule = getExecutorService().schedule(this, delay, unit);
        stateProperty().set(TaskStatus.DELAYED);
        return schedule;
    }

    private void updateMessage(String string) {
        messageProperty().set(string);
    }

    public boolean cancel() {
        if (delegate.cancel()) {
            stateProperty().set(TaskStatus.CANCELLED);
            return true;
        }
        return false;
    }

    /**
     * 获取任务执行耗时
     */
    public long getExecutionTime() {
        return System.currentTimeMillis() - startTime;
    }

    public boolean isFailed() {
        return delegate.getState() == javafx.concurrent.Worker.State.FAILED;
    }

    public boolean isSucceeded() {
        return delegate.getState() == javafx.concurrent.Worker.State.SUCCEEDED;
    }

    public boolean isRunning() {
        return delegate.getState() == javafx.concurrent.Worker.State.RUNNING;
    }

    public void setTimeout(Long timeoutSeconds2) {
        this.timeoutSeconds = timeoutSeconds2;
    }

    public void setOnSucceeded(Runnable value) {
        succeededProperty.set(value);
    }

    public void setOnFailed(Runnable value) {
        failedProperty.set(value);
    }

    public void setOnCancelled(Runnable value) {
        cancelledHandler.set(value);
    }

    public void setOnRunning(EventHandler<WorkerStateEvent> value) {
        delegate.setOnRunning(value);
    }

    ScheduledExecutorService getExecutorService() {
        return Desktop.instance.getExecutorService();
    }

}