package savvytodo.model.task;

import java.time.DateTimeException;
import java.time.LocalDateTime;

import savvytodo.commons.exceptions.IllegalValueException;
import savvytodo.commons.util.DateTimeUtil;
import savvytodo.commons.util.StringUtil;

/**
 * @author A0140016B
 *
 * Represents Task's DateTime in the task manager Guarantees: immutable;
 * is valid as declared in {@link #isValidEvent(String, String)} *
 */
public class DateTime implements Comparable<DateTime> {

    public String startValue;
    public String endValue;
    public String timestamp;

    private LocalDateTime start;
    private LocalDateTime end;
    private LocalDateTime add;

    public static final String MESSAGE_DATETIME_CONSTRAINTS = "Start date/time should not be after End date/time";

    private static final int COMPARE_TO_SMALLER = -1;
    private static final int COMPARE_TO_EQUAL = 0;
    private static final int COMPARE_TO_GREATER = 1;

    public static final String DATETIME_STRING_CONNECTOR = " = ";
    private static final String DATETIME_STRING_TO_STRING_CONNECTOR = " ~ ";
    public static final String[] DEFAULT_VALUES = {StringUtil.EMPTY_STRING, StringUtil.EMPTY_STRING};

    /**
     * Default constructor
     * @throws DateTimeException
     */
    public DateTime() throws IllegalValueException {
        this(DEFAULT_VALUES);
    }

    /**
     * Validates given DateTime.
     * If DateTime is NULL, means that the task is not an event
     * @throws IllegalValueException end date occurring before start date.
     */
    public DateTime(String startDateTime, String endDateTime) throws IllegalValueException {
        if (endDateTime != null && !endDateTime.isEmpty()) {
            this.end = LocalDateTime.parse(endDateTime.trim(), DateTimeUtil.DATE_FORMATTER);
            this.endValue = this.end.format(DateTimeUtil.DATE_STRING_FORMATTER);
        } else {
            this.endValue = endDateTime;
        }
        if (startDateTime != null && !startDateTime.isEmpty()) {
            this.start = LocalDateTime.parse(startDateTime.trim(), DateTimeUtil.DATE_FORMATTER);
            this.startValue = this.start.format(DateTimeUtil.DATE_STRING_FORMATTER);
            if (!isValidEvent(this.start, this.end)) {
                throw new IllegalValueException(MESSAGE_DATETIME_CONSTRAINTS);
            }
        } else {
            this.startValue = startDateTime;
        }
        this.setDateTimeStamp();
    }

    public DateTime(LocalDateTime startDateTime, LocalDateTime endDateTime) {
        assert (startDateTime != null && endDateTime != null);
        this.start = startDateTime;
        this.end = endDateTime;
        this.endValue = this.end.format(DateTimeUtil.DATE_STRING_FORMATTER);
        this.startValue = this.start.format(DateTimeUtil.DATE_STRING_FORMATTER);
        this.setDateTimeStamp();
    }

    /**
     * Constructor when given input as String array
     * @param dateTime
     * @throws IllegalValueException
     * @throws DateTimeException
     */
    public DateTime(String[] dateTime) throws DateTimeException, IllegalValueException {
        this(dateTime[0], dateTime[1]);
    }

    /**
     * Set timestamp if exists
     */
    private void setDateTimeStamp() {
        if (timestamp == null || timestamp.isEmpty()) {
            this.setAdd(LocalDateTime.now());
        } else {
            this.setAdd(LocalDateTime.parse(timestamp));
        }
    }

    /**
     * Returns true if a given string is a valid task event.
     */
    public static boolean isValidEvent(LocalDateTime startDateTime, LocalDateTime endDateTime) {
        return (startDateTime.isBefore(endDateTime));
    }

    @Override
    public String toString() {
        if (this.start != null && this.end != null) {
            return startValue + DATETIME_STRING_TO_STRING_CONNECTOR + endValue;
        } else if (this.end != null) {
            return endValue;
        } else {
            return StringUtil.EMPTY_STRING;
        }
    }

    @Override
    public boolean equals(Object other) {
        if (other == null) return false;
        if (other == this) return true;
        if (!(other instanceof DateTime)) return false;
        DateTime o = (DateTime) other;

        if (this.getEndDate() == null && o.getEndDate() == null) {
            return true;
        } else if ((this.getStartDate() == null && o.getStartDate() == null)
                && (this.getEndDate() != null && o.getEndDate() != null)) {
            return (this.getEndDate().equals(o.getEndDate()));
        } else if (this.getStartDate() != null && o.getStartDate() != null && this.getEndDate() != null
                && o.getEndDate() != null) {
            return (this.getStartDate().equals(o.getStartDate()) && this.getEndDate().equals(o.getEndDate()));
        }

        return false;
    }

    @Override
    public int compareTo(DateTime o) {
        if (this.start == null && this.end == null) {
            if (o.start == null && o.end == null) {
                return COMPARE_TO_EQUAL;
            } else {
                return COMPARE_TO_SMALLER;
            }
        }

        if (this.start == null && this.end != null) {
            if (o.start != null && o.end != null) {
                return this.end.compareTo(o.start);
            } else if (o.end != null) {
                return this.end.compareTo(o.end);
            } else {
                return COMPARE_TO_GREATER;
            }
        }

        if (this.start != null && this.end != null) {
            if (o.start != null && o.end != null) {
                int result = this.start.compareTo(o.start);
                if (result == COMPARE_TO_EQUAL) {
                    return this.end.compareTo(o.end);
                } else {
                    return result;
                }
            } else if (o.start == null && o.end != null) {
                return this.start.compareTo(o.end);
            } else {
                return COMPARE_TO_GREATER;
            }
        }

        return COMPARE_TO_EQUAL;
    }

    public LocalDateTime getStartDate() {
        return start;
    }

    public LocalDateTime getEndDate() {
        return end;
    }

    public LocalDateTime getDateTimeAdded() {
        return add;
    }

    /**
     * Set method for start
     * @param LocalDateTime
     */
    public void setStart(LocalDateTime startDateTime) {
        this.start = startDateTime;
    }

    /**
     * Set method for end
     * @param LocalDateTime
     */
    public void setEnd(LocalDateTime endDateTime) {
        this.end = endDateTime;
    }

    /**
     * Set method for add
     * @param LocalDateTime
     */
    public void setAdd(LocalDateTime dateTimeAdded) {
        timestamp = dateTimeAdded.toString();
        this.add = dateTimeAdded;
    }

}

@@ -1121,16 +1121,17 @@
 LUES = %7B
+ 
 StringUt
@@ -1146,16 +1146,28 @@
 _STRING,
+%0A           
  StringU
@@ -1182,16 +1182,17 @@
 Y_STRING
+ 
 %7D;%0A%0A    
@@ -4068,16 +4068,28 @@
 == null)
+%0A           
  return 
@@ -4121,16 +4121,28 @@
 == this)
+%0A           
  return 
@@ -4188,16 +4188,28 @@
 teTime))
+%0A           
  return 
@@ -4629,32 +4629,48 @@
 rtDate() != null
+%0A               
  && this.getEndD
@@ -4682,32 +4682,16 @@
  != null
-%0A               
  && o.ge
@@ -4776,16 +4776,36 @@
 tDate())
+%0A                   
  && this
@@ -6806,24 +6806,24 @@
 toString();%0A
-
         this
@@ -6851,10 +6851,121 @@
 %0A    %7D%0A%0A
+    @Override%0A    public int hashCode() %7B%0A        return (this.start.hashCode() + this.end.hashCode());%0A    %7D%0A%0A
 %7D%0A

resolve conflict