package info.opencards.core;
import info.opencards.core.categories.Category;
import info.opencards.pptintegration.PPTSerializer;
import info.opencards.ui.AbstractLearnDialog;
import org.apache.poi.hslf.usermodel.HSLFSlideShow;
import org.apache.poi.sl.usermodel.SlideShow;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
/**
* A wrapper for an existing file which contains some kind of flashcards (e.g. an Impress-presentation). Different
* internal representations of these cards may be contained in the file to which an instance of this clas links to. This
* is necessary in order to use differnt learning-approaches (LTM/STM-learning) in parallel.
* <p/>
* The implementation itself is not bounded to a specific card-file-format. Instead the necessary API to extract the
* flashcard information from the file is encapsulated in an <code>CardFileSerializer</code> which need to be specified
* using {@link CardFile#setSerializer(LearnStatusSerializer)} during runtime.
*
* @author Holger Brandl
*/
public class CardFile {
private final List<Category> myCats = new ArrayList<Category>();
private File fileLocation;
/**
* Indicates when this instance was synced with the associated card-file.
*/
private Date lastSync = null;
private transient FlashCardCollection fileItems;
private transient LearnStatusSerializer serializer = new PPTSerializer();
// just needed for mak
private CardFileProperties cfp = new CardFileProperties();
public CardFile(File cardFileLocation) {
assert cardFileLocation != null;
this.fileLocation = cardFileLocation;
}
public static SlideShow getSlideShow(CardFile cardFile) {
try {
FileInputStream is = new FileInputStream(cardFile.getFileLocation());
return new HSLFSlideShow(is);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
* Returns a reference to list of all categories this card-file is registered to.
*/
public Collection<Category> belongsTo() {
return new ArrayList<Category>(myCats);
}
public boolean addToCategory(Category c) {
// if (!this.belongsTo().contains(c))
return myCats.add(c);
// return false;
}
public boolean removeFromCategory(Category c) {
assert myCats.contains(c);
myCats.remove(c);
return true;
}
public File getFileLocation() {
return fileLocation;
}
public String toString() {
// String catString = myCats.toString();
// return (fileLocation != null ? fileLocation.toString() : "inmemory card-set") + " (" + catString + ")";
return fileLocation.toString();
}
public boolean equals(Object obj) {
if (!(obj instanceof CardFile))
return false;
CardFile file = (CardFile) obj;
return file.getFileLocation().equals(getFileLocation());
}
public FlashCardCollection getFlashCards() {
if (fileItems == null) {
assert serializer != null : "serializer not set";
fileItems = serializer.deserializeFileCards(this);
if (fileItems == null) {
fileItems = serializer.readFlashcardsFromFile(this);
this.flush();
}
}
return fileItems;
}
public boolean isDeserialized() {
return !(fileItems == null);
}
public void setSerializer(LearnStatusSerializer serializer) {
if (this.serializer != null && this.serializer.equals(serializer))
return;
this.serializer = serializer;
fileItems = null;
}
public LearnStatusSerializer getSerializer() {
return serializer;
}
public void flush() {
if (fileItems == null) {
throw new RuntimeException("can not serialize null fileItems");
}
// serialize to xml and then gzip the result
serializer.serializeFileCards(this, fileItems);
}
public boolean forceSync() {
lastSync = null;
return synchronize();
}
/**
* Given a serialzer and a this methods merges changes of the deep serialzer into an existing representation.
*
* @return <code>true</code> if a synchronization was necessary. This means that some cards were either added or
* removed since the last use of OpenCards.
*/
public boolean synchronize() {
if (serializer == null) {
throw new RuntimeException("no serializer set for '" + this + "'");
}
if (wasModifiedAfterSync()) {
return false;
}
boolean neededSync = false;
FlashCardCollection syncCards = serializer.readFlashcardsFromFile(this);
fileItems = getFlashCards();
// now lets do some cool synchronization!!
// now do a three way synchronization
// 1) add missing (new, recently) added cards
for (FlashCard flashCard : syncCards) {
if (!fileItems.contains(flashCard)) {
fileItems.add(flashCard);
neededSync = true;
}
}
// 2) remove no longer present cards
for (int i = 0; i < fileItems.size(); i++) {
FlashCard flashCard = fileItems.get(i);
if (!syncCards.contains(flashCard)) {
fileItems.remove(flashCard);
// tag the card as deleted
flashCard.setCardIndex(AbstractLearnDialog.INVALID_ITEM);
neededSync = true;
i--;
}
}
assert syncCards.size() == fileItems.size() : "size difference after synchronization";
// 3) fix slide positions and titles of items if necessary
for (FlashCard card : fileItems) {
FlashCard flashcard = syncCards.get(syncCards.indexOf(card));
if (flashcard.getCardIndex() != card.getCardIndex())
card.setCardIndex(flashcard.getCardIndex());
// for now no longer neccessay as the title defines the card id
// if (!flashcard.getCardTitle().equals(card.getCardTitle()))
// card.setCardTitle(flashcard.getCardTitle());
}
lastSync = new Date(System.currentTimeMillis());
return neededSync;
}
private boolean wasModifiedAfterSync() {
return lastSync != null && lastSync.after(new Date(getFileLocation().lastModified()));
}
public CardFileProperties getProperties() {
// legacy format support
if (cfp == null) cfp = new CardFileProperties();
return cfp;
}
}