/*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker 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.
*
* Loop Habit Tracker 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.models;
import android.support.annotation.*;
import com.opencsv.*;
import org.isoron.uhabits.utils.*;
import java.io.*;
import java.util.*;
/**
* An ordered collection of {@link Habit}s.
*/
public abstract class HabitList implements Iterable<Habit>
{
private ModelObservable observable;
@NonNull
protected final HabitMatcher filter;
/**
* Creates a new HabitList.
* <p>
* Depending on the implementation, this list can either be empty or be
* populated by some pre-existing habits, for example, from a certain
* database.
*/
public HabitList()
{
observable = new ModelObservable();
filter = new HabitMatcherBuilder().setArchivedAllowed(true).build();
}
protected HabitList(@NonNull HabitMatcher filter)
{
observable = new ModelObservable();
this.filter = filter;
}
/**
* Inserts a new habit in the list.
* <p>
* If the id of the habit is null, the list will assign it a new id, which
* is guaranteed to be unique in the scope of the list. If id is not null,
* the caller should make sure that the list does not already contain
* another habit with same id, otherwise a RuntimeException will be thrown.
*
* @param habit the habit to be inserted
* @throws IllegalArgumentException if the habit is already on the list.
*/
public abstract void add(@NonNull Habit habit)
throws IllegalArgumentException;
/**
* Returns the habit with specified id.
*
* @param id the id of the habit
* @return the habit, or null if none exist
*/
@Nullable
public abstract Habit getById(long id);
/**
* Returns the habit that occupies a certain position.
*
* @param position the position of the desired habit
* @return the habit at that position
* @throws IndexOutOfBoundsException when the position is invalid
*/
@NonNull
public abstract Habit getByPosition(int position);
/**
* Returns the list of habits that match a given condition.
*
* @param matcher the matcher that checks the condition
* @return the list of matching habits
*/
@NonNull
public abstract HabitList getFiltered(HabitMatcher matcher);
public ModelObservable getObservable()
{
return observable;
}
public abstract Order getOrder();
/**
* Changes the order of the elements on the list.
*
* @param order the new order criterea
*/
public abstract void setOrder(@NonNull Order order);
/**
* Returns the index of the given habit in the list, or -1 if the list does
* not contain the habit.
*
* @param h the habit
* @return the index of the habit, or -1 if not in the list
*/
public abstract int indexOf(@NonNull Habit h);
public boolean isEmpty()
{
return size() == 0;
}
/**
* Removes the given habit from the list.
* <p>
* If the given habit is not in the list, does nothing.
*
* @param h the habit to be removed.
*/
public abstract void remove(@NonNull Habit h);
/**
* Removes all the habits from the list.
*/
public void removeAll()
{
List<Habit> copy = new LinkedList<>();
for (Habit h : this) copy.add(h);
for (Habit h : copy) remove(h);
}
/**
* Changes the position of a habit in the list.
*
* @param from the habit that should be moved
* @param to the habit that currently occupies the desired position
*/
public abstract void reorder(Habit from, Habit to);
public void repair()
{
for (Habit h : this)
{
h.getCheckmarks().invalidateNewerThan(0);
h.getStreaks().invalidateNewerThan(0);
h.getScores().invalidateNewerThan(0);
}
}
/**
* Returns the number of habits in this list.
*
* @return number of habits
*/
public abstract int size();
/**
* Notifies the list that a certain list of habits has been modified.
* <p>
* Depending on the implementation, this operation might trigger a write to
* disk, or do nothing at all. To make sure that the habits get persisted,
* this operation must be called.
*
* @param habits the list of habits that have been modified.
*/
public abstract void update(List<Habit> habits);
/**
* Notifies the list that a certain habit has been modified.
* <p>
* See {@link #update(List)} for more details.
*
* @param habit the habit that has been modified.
*/
public void update(@NonNull Habit habit)
{
update(Collections.singletonList(habit));
}
/**
* Writes the list of habits to the given writer, in CSV format. There is
* one line for each habit, containing the fields name, description,
* frequency numerator, frequency denominator and color. The color is
* written in HTML format (#000000).
*
* @param out the writer that will receive the result
* @throws IOException if write operations fail
*/
public void writeCSV(@NonNull Writer out) throws IOException
{
String header[] = {
"Position",
"Name",
"Description",
"NumRepetitions",
"Interval",
"Color"
};
CSVWriter csv = new CSVWriter(out);
csv.writeNext(header, false);
for (Habit habit : this)
{
Frequency freq = habit.getFrequency();
String[] cols = {
String.format("%03d", indexOf(habit) + 1),
habit.getName(),
habit.getDescription(),
Integer.toString(freq.getNumerator()),
Integer.toString(freq.getDenominator()),
ColorUtils.CSV_PALETTE[habit.getColor()]
};
csv.writeNext(cols, false);
}
csv.close();
}
public enum Order
{
BY_NAME,
BY_COLOR,
BY_SCORE,
BY_POSITION
}
}