package seng302.Model;

import com.github.bfsmith.geotimezone.TimeZoneLookup;
import com.github.bfsmith.geotimezone.TimeZoneResult;
import javafx.animation.AnimationTimer;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import seng302.GPSCoordinate;

import java.time.Duration;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.Date;

/**
 * Created by Gondr on 19/04/2017.
 */
public class RaceClock implements Runnable {
    private long lastTime;
    private final ZoneId zoneId;
    private ZonedDateTime time;
    private ZonedDateTime startingTime;
    private final StringProperty timeString;
    private StringProperty duration;

    public RaceClock(ZonedDateTime zonedDateTime) {
        this.zoneId = zonedDateTime.getZone();
        this.timeString = new SimpleStringProperty();
        this.duration = new SimpleStringProperty();
        this.time = zonedDateTime;
        setTime(time);
    }

    public static ZonedDateTime getCurrentZonedDateTime(GPSCoordinate gpsCoordinate) {
        TimeZoneLookup timeZoneLookup = new TimeZoneLookup();
        TimeZoneResult timeZoneResult = timeZoneLookup.getTimeZone(gpsCoordinate.getLatitude(), gpsCoordinate.getLongitude());
        ZoneId zone = ZoneId.of(timeZoneResult.getResult());
        return LocalDateTime.now(zone).atZone(zone);
    }

    public void run() {
        new AnimationTimer() {
            @Override
            public void handle(long now) {
                updateTime();
            }
        }.start();
    }

    /**
     * Sets time to arbitrary zoned time.
     *
     * @param time arbitrary time with timezone.
     */
    private void setTime(ZonedDateTime time) {
        this.time = time;
        this.timeString.set(DateTimeFormatter.ofPattern("HH:mm:ss dd/MM/YYYY Z").format(time));
        this.lastTime = System.currentTimeMillis();


        if(startingTime != null) {
            long seconds = Duration.between(startingTime.toLocalDateTime(), time.toLocalDateTime()).getSeconds();
            if(seconds < 0)
                duration.set(String.format("Starting in: %02d:%02d:%02d", -seconds/3600, -(seconds%3600)/60, -seconds%60));
            else
                duration.set(String.format("Time: %02d:%02d:%02d", seconds/3600, (seconds%3600)/60, seconds%60));
        }

    }

    /**
     * Sets time to given UTC time in seconds from Unix epoch, preserving timezone.
     * @param time UTC time
     */
    public void setUTCTime(long time) {
        Date utcTime = new Date(time);
        setTime(utcTime.toInstant().atZone(this.zoneId));
    }

    public ZonedDateTime getStartingTime() {
        return startingTime;
    }

    public void setStartingTime(ZonedDateTime startingTime) {
        this.startingTime = startingTime;
    }

    /**
     * Get ZonedDateTime corresponding to local time zone and given UTC time.
     * @param time time in mills
     * @return local date time
     */
    public ZonedDateTime getLocalTime(long time) {
        Date utcTime = new Date(time);
        return utcTime.toInstant().atZone(this.zoneId);
    }

    /**
     * Updates time by duration elapsed since last update.
     */
    private void updateTime() {
        this.time = this.time.plus(Duration.of(System.currentTimeMillis() - this.lastTime, ChronoUnit.MILLIS));
        this.lastTime = System.currentTimeMillis();
        setTime(time);
    }

    public String getDuration() {
        return duration.get();
    }

    public StringProperty durationProperty() {
        return duration;
    }

    public String getTimeZone() {
        return zoneId.toString();
    }

    public ZonedDateTime getTime() {
        return time;
    }

    public StringProperty timeStringProperty() {
        return timeString;
    }
}
