// Path: app/src/main/java/com/philliphsu/clock2/data/ObjectWithId.java
// public abstract class ObjectWithId {
//     private long id;
// 
//     public final long getId() {
//         return id;
//     }
// 
//     public final void setId(long id) {
//         this.id = id;
//     }
// 
//     public final int getIntId() {
//         return (int) id;
//     }
// }
// 
// Path: app/src/main/java/com/philliphsu/clock2/alarms/misc/DaysOfWeek.java
// public static final int NUM_DAYS  = 7;
// 
// Path: app/src/main/java/com/philliphsu/clock2/alarms/misc/DaysOfWeek.java
// public static final int SATURDAY  = 6;
// 
// Path: app/src/main/java/com/philliphsu/clock2/alarms/misc/DaysOfWeek.java
// public static final int SUNDAY    = 0;


import static com.philliphsu.clock2.alarms.misc.DaysOfWeek.NUM_DAYS;
import static com.philliphsu.clock2.alarms.misc.DaysOfWeek.SATURDAY;
import static com.philliphsu.clock2.alarms.misc.DaysOfWeek.SUNDAY;
import android.os.Parcel;
import android.os.Parcelable;
import com.google.auto.value.AutoValue;
import com.philliphsu.clock2.data.ObjectWithId;
import org.json.JSONObject;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.concurrent.TimeUnit;
/*
 * Copyright 2017 Phillip Hsu
 *
 * This file is part of ClockPlus.
 *
 * ClockPlus 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.
 *
 * ClockPlus 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 ClockPlus.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.philliphsu.clock2.alarms;






/**
 * Created by Phillip Hsu on 5/26/2016.
 */
@AutoValue
public abstract class Alarm extends ObjectWithId implements Parcelable {
    private static final int MAX_MINUTES_CAN_SNOOZE = 30;

    // =================== MUTABLE =======================
    private long snoozingUntilMillis;
    private boolean enabled;
    private final boolean[] recurringDays = new boolean[NUM_DAYS];
    private boolean ignoreUpcomingRingTime;
    // ====================================================

    public abstract int hour();
    public abstract int minutes();
    public abstract String label();
    public abstract String ringtone();
    public abstract boolean vibrates();
    /** Initializes a Builder to the same property values as this instance */
    public abstract Builder toBuilder();

    @Deprecated
    public static Alarm create(JSONObject jsonObject) {
        throw new UnsupportedOperationException();
    }

    public void copyMutableFieldsTo(Alarm target) {
        target.setId(this.getId());
        target.snoozingUntilMillis = this.snoozingUntilMillis;
        target.enabled = this.enabled;
        System.arraycopy(this.recurringDays, 0, target.recurringDays, 0, NUM_DAYS);
        target.ignoreUpcomingRingTime = this.ignoreUpcomingRingTime;
    }

    public static Builder builder() {
        // Unfortunately, default values must be provided for generated Builders.
        // Fields that were not set when build() is called will throw an exception.
        return new AutoValue_Alarm.Builder()
                .hour(0)
                .minutes(0)
                .label("")
                .ringtone("")
                .vibrates(false);
    }

    public void snooze(int minutes) {
        if (minutes <= 0 || minutes > MAX_MINUTES_CAN_SNOOZE)
            throw new IllegalArgumentException("Cannot snooze for "+minutes+" minutes");
        snoozingUntilMillis = System.currentTimeMillis() + minutes * 60000;
    }

    public long snoozingUntil() {
        return isSnoozed() ? snoozingUntilMillis : 0;
    }

    public boolean isSnoozed() {
        if (snoozingUntilMillis <= System.currentTimeMillis()) {
            snoozingUntilMillis = 0;
            return false;
        }
        return true;
    }

    /** <b>ONLY CALL THIS WHEN CREATING AN ALARM INSTANCE FROM A CURSOR</b> */
    // TODO: To be even more safe, create a ctor that takes a Cursor and
    // initialize the instance here instead of in AlarmDatabaseHelper.
    public void setSnoozing(long snoozingUntilMillis) {
        this.snoozingUntilMillis = snoozingUntilMillis;
    }

    public void stopSnoozing() {
        snoozingUntilMillis = 0;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    public boolean isEnabled() {
        return enabled;
    }

    public boolean[] recurringDays() {
        return recurringDays;
    }

    public void setRecurring(int day, boolean recurring) {
        checkDay(day);
        recurringDays[day] = recurring;
    }

    public boolean isRecurring(int day) {
        checkDay(day);
        return recurringDays[day];
    }

    public boolean hasRecurrence() {
        return numRecurringDays() > 0;
    }

    public int numRecurringDays() {
        int count = 0;
        for (boolean b : recurringDays)
            if (b) count++;
        return count;
    }

    public void ignoreUpcomingRingTime(boolean ignore) {
        ignoreUpcomingRingTime = ignore;
    }

    public boolean isIgnoringUpcomingRingTime() {
        return ignoreUpcomingRingTime;
    }

    public long ringsAt() {
        // Always with respect to the current date and time
        Calendar calendar = new GregorianCalendar();
        calendar.set(Calendar.HOUR_OF_DAY, hour());
        calendar.set(Calendar.MINUTE, minutes());
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);

        long baseRingTime = calendar.getTimeInMillis();

        if (!hasRecurrence()) {
            if (baseRingTime <= System.currentTimeMillis()) {
                // The specified time has passed for today
                baseRingTime += TimeUnit.DAYS.toMillis(1);
            }
            return baseRingTime;
        } else {
            // Compute the ring time just for the next closest recurring day.
            // Remember that day constants defined in the Calendar class are
            // not zero-based like ours, so we have to compensate with an offset
            // of magnitude one, with the appropriate sign based on the situation.
            int weekdayToday = calendar.get(Calendar.DAY_OF_WEEK);
            int numDaysFromToday = -1;

// Path: app/src/main/java/com/philliphsu/clock2/data/ObjectWithId.java
// public abstract class ObjectWithId {
//     private long id;
// 
//     public final long getId() {
//         return id;
//     }
// 
//     public final void setId(long id) {
//         this.id = id;
//     }
// 
//     public final int getIntId() {
//         return (int) id;
//     }
// }
// 
// Path: app/src/main/java/com/philliphsu/clock2/alarms/misc/DaysOfWeek.java
// public static final int NUM_DAYS  = 7;
// 
// Path: app/src/main/java/com/philliphsu/clock2/alarms/misc/DaysOfWeek.java
// public static final int SATURDAY  = 6;
// 
// Path: app/src/main/java/com/philliphsu/clock2/alarms/misc/DaysOfWeek.java
// public static final int SUNDAY    = 0;


// Path: app/src/main/java/com/philliphsu/clock2/alarms/Alarm.java
import static com.philliphsu.clock2.alarms.misc.DaysOfWeek.NUM_DAYS;
import static com.philliphsu.clock2.alarms.misc.DaysOfWeek.SATURDAY;
import static com.philliphsu.clock2.alarms.misc.DaysOfWeek.SUNDAY;
import android.os.Parcel;
import android.os.Parcelable;
import com.google.auto.value.AutoValue;
import com.philliphsu.clock2.data.ObjectWithId;
import org.json.JSONObject;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.concurrent.TimeUnit;

/*
 * Copyright 2017 Phillip Hsu
 *
 * This file is part of ClockPlus.
 *
 * ClockPlus 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.
 *
 * ClockPlus 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 ClockPlus.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.philliphsu.clock2.alarms;






/**
 * Created by Phillip Hsu on 5/26/2016.
 */
@AutoValue
public abstract class Alarm extends ObjectWithId implements Parcelable {
    private static final int MAX_MINUTES_CAN_SNOOZE = 30;

    // =================== MUTABLE =======================
    private long snoozingUntilMillis;
    private boolean enabled;
    private final boolean[] recurringDays = new boolean[NUM_DAYS];
    private boolean ignoreUpcomingRingTime;
    // ====================================================

    public abstract int hour();
    public abstract int minutes();
    public abstract String label();
    public abstract String ringtone();
    public abstract boolean vibrates();
    /** Initializes a Builder to the same property values as this instance */
    public abstract Builder toBuilder();

    @Deprecated
    public static Alarm create(JSONObject jsonObject) {
        throw new UnsupportedOperationException();
    }

    public void copyMutableFieldsTo(Alarm target) {
        target.setId(this.getId());
        target.snoozingUntilMillis = this.snoozingUntilMillis;
        target.enabled = this.enabled;
        System.arraycopy(this.recurringDays, 0, target.recurringDays, 0, NUM_DAYS);
        target.ignoreUpcomingRingTime = this.ignoreUpcomingRingTime;
    }

    public static Builder builder() {
        // Unfortunately, default values must be provided for generated Builders.
        // Fields that were not set when build() is called will throw an exception.
        return new AutoValue_Alarm.Builder()
                .hour(0)
                .minutes(0)
                .label("")
                .ringtone("")
                .vibrates(false);
    }

    public void snooze(int minutes) {
        if (minutes <= 0 || minutes > MAX_MINUTES_CAN_SNOOZE)
            throw new IllegalArgumentException("Cannot snooze for "+minutes+" minutes");
        snoozingUntilMillis = System.currentTimeMillis() + minutes * 60000;
    }

    public long snoozingUntil() {
        return isSnoozed() ? snoozingUntilMillis : 0;
    }

    public boolean isSnoozed() {
        if (snoozingUntilMillis <= System.currentTimeMillis()) {
            snoozingUntilMillis = 0;
            return false;
        }
        return true;
    }

    /** <b>ONLY CALL THIS WHEN CREATING AN ALARM INSTANCE FROM A CURSOR</b> */
    // TODO: To be even more safe, create a ctor that takes a Cursor and
    // initialize the instance here instead of in AlarmDatabaseHelper.
    public void setSnoozing(long snoozingUntilMillis) {
        this.snoozingUntilMillis = snoozingUntilMillis;
    }

    public void stopSnoozing() {
        snoozingUntilMillis = 0;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    public boolean isEnabled() {
        return enabled;
    }

    public boolean[] recurringDays() {
        return recurringDays;
    }

    public void setRecurring(int day, boolean recurring) {
        checkDay(day);
        recurringDays[day] = recurring;
    }

    public boolean isRecurring(int day) {
        checkDay(day);
        return recurringDays[day];
    }

    public boolean hasRecurrence() {
        return numRecurringDays() > 0;
    }

    public int numRecurringDays() {
        int count = 0;
        for (boolean b : recurringDays)
            if (b) count++;
        return count;
    }

    public void ignoreUpcomingRingTime(boolean ignore) {
        ignoreUpcomingRingTime = ignore;
    }

    public boolean isIgnoringUpcomingRingTime() {
        return ignoreUpcomingRingTime;
    }

    public long ringsAt() {
        // Always with respect to the current date and time
        Calendar calendar = new GregorianCalendar();
        calendar.set(Calendar.HOUR_OF_DAY, hour());
        calendar.set(Calendar.MINUTE, minutes());
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);

        long baseRingTime = calendar.getTimeInMillis();

        if (!hasRecurrence()) {
            if (baseRingTime <= System.currentTimeMillis()) {
                // The specified time has passed for today
                baseRingTime += TimeUnit.DAYS.toMillis(1);
            }
            return baseRingTime;
        } else {
            // Compute the ring time just for the next closest recurring day.
            // Remember that day constants defined in the Calendar class are
            // not zero-based like ours, so we have to compensate with an offset
            // of magnitude one, with the appropriate sign based on the situation.
            int weekdayToday = calendar.get(Calendar.DAY_OF_WEEK);
            int numDaysFromToday = -1;

            for (int i = weekdayToday; i <= Calendar.SATURDAY; i++) {