aboutsummaryrefslogtreecommitdiffstats
path: root/src/eu/equalparts/cardbase/Cardbase.java
blob: bdbdeb82504712af6a7e8ee2f4d87880c393a0f9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
package eu.equalparts.cardbase;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonMappingException;

import eu.equalparts.cardbase.cards.Card;
import eu.equalparts.cardbase.comparator.CardComparator;
import eu.equalparts.cardbase.decks.ReferenceDeck;
import eu.equalparts.cardbase.decks.StandaloneDeck;
import eu.equalparts.cardbase.utils.JSON;

/**
 * Provides a variety of utility methods to interact with an optionally loaded cardbase.
 * 
 * @author Eduardo Pedroni
 */
public class Cardbase {
	
	/**
	 * The cards in the cardbase, set in key-value pairs where the key is the card hash,
	 * generated using {makeHash()}.
	 */
	private Map<Integer, Card> cards;
	/**
	 * The decks which have been saved along with this collection of cards.
	 */
	private Map<String, ReferenceDeck> decks;
	/**
	 * Debug flag is raised when the DEBUG environment variable is set. This causes additional
	 * information to be printed to the console.
	 */
	public static final boolean DEBUG = System.getenv("CB_DEBUG") != null;
	
	/**
	 * Initialises the cardbase with the contents of a file.
	 *
	 * @param cardbaseFile the cardbase JSON to load.
	 * 
	 * @throws JsonParseException if the specified file does not contain valid JSON.
	 * @throws JsonMappingException if the specified file structure does not match that of {@code Cardbase}.
	 * @throws IOException if a low-level I/O problem (unexpected end-of-input, network error) occurs.
	 */
	public Cardbase(File cardbaseFile) throws JsonParseException, JsonMappingException, IOException {
		cards = JSON.mapper.readValue(cardbaseFile, new TypeReference<Map<Integer, Card>>() {});
	}
	
	/**
	 * Initialises a clean cardbase.
	 */
	public Cardbase() {
		cards = new HashMap<Integer, Card>();
		decks = new HashMap<String, ReferenceDeck>();
	}

	/**
	 * Writes the provided {@code Cardbase} to the provided file in JSON format.
	 * 
	 * @param file the file to which to write the {@code Cardbase}.
	 * @param cardbase the {@code Cardbase} to write out.
	 * 
	 * @throws JsonGenerationException if the data structure given does not generate valid JSON.
	 * @throws JsonMappingException if the data structure given does not generate valid JSON as well?
	 * @throws IOException if a low-level I/O problem (unexpected end-of-input, network error) occurs.
	 */
	public void writeCollection(File outputFile) throws JsonGenerationException, JsonMappingException, IOException {
		JSON.mapper.writeValue(outputFile, cards);
	}

	/**
	 * Adds a specific amount of a card to the cardbase.
	 * If the card is not already in the cardbase, it is added.
	 * If it is already present, the count is simply updated.
	 * 
	 * @param cardToAdd the card to be added. The count value
	 * of this object is added to the existing count if the card
	 * already exists.
	 */
	public void addCard(Card cardToAdd) {
		Card card = getCardByHash(cardToAdd.hashCode());
		if (card != null) {
			card.count += cardToAdd.count;
		} else {
			cards.put(cardToAdd.hashCode(), cardToAdd);
		}
	}

	/**
	 * Removes a specific amount of a card from the cardbase.
	 * If the card is not present in the cardbase, nothing happens.
	 * If the card is present in the cardbase, the specified amount is removed.
	 * If that amount is equal to or exceeds the count already in the cardbase,
	 * the card entry is removed altogether.
	 * <br><br>
	 * In any case, the value returned is the actual number of cards removed.
	 * For example, if 5 Shivan Dragons are in the cardbase and the method is
	 * called to remove 10 Shivan Dragons, the {@code Card} representing the
	 * Shivan Dragon is removed from the cardbase, and the value returned is 5.
	 * 
	 * @param cardToRemove the card to be removed.
	 * @param count the amount of the card to be removed.
	 * @return the number of cards actually removed.
	 */
	public Integer removeCard(Card cardToRemove) {
		Card card = getCardByHash(cardToRemove.hashCode());
		Integer removed = 0;
		if (card != null) {
			if (card.count <= cardToRemove.count) {
				cards.remove(card.hashCode());
				removed = card.count;
			} else {
				card.count -= cardToRemove.count;
				removed = cardToRemove.count;
			}
		}
		return removed;
	}

//	/**
//	 * Returns a card from the cardbase by set code and number.
//	 * If no such card is in the cardbase, returns null.
//	 * 
//	 * @param setCode the set to which the requested card belongs.
//	 * @param number the requested card's set number.
//	 * @return the requested {@code Card} or null if no card is found.
//	 */
	public Card getCard(String setCode, String number) {
		return cards.get(Card.makeHash(setCode, number));
	}
	
	/**
	 * Returns a card from the cardbase by hash. The card's hash
	 * can be generated using {@code Cardbase.makeHash()}.
	 * If no such card is in the cardbase, returns null.
	 * 
	 * @param hash the Cardbase hash of the requested card.
	 * @return the requested {@code Card} or null if no card is found.
	 */
	protected Card getCardByHash(Integer hash) {
		return cards.get(hash);
	}
	
	/**
	 * This method is intended to allow iteration directly on the list of cards,
	 * while at the same time retaining control over the insert and remove procedures.
	 * The returned {@code List} is a read-only; trying to modify its structure will
	 * result in an {@code UnsupportedOperationException}.
	 * 
	 * @return an unmodifiable list of all the cards in the cardbase.
	 */
	public Collection<Card> getCards() {
		return Collections.unmodifiableCollection(cards.values());
	}
	
	/**
	 * @param field the name of the field by which to sort.
	 * @return an unmodifiable collection representing the cardbase sorted in the required order.
	 * @throws NoSuchFieldException if the field provided is invalid.
	 */
	public Collection<Card> sort(String field) throws NoSuchFieldException {
		List<Card> sortedCards = new ArrayList<Card>(cards.values());
		sortedCards.sort(new CardComparator(Card.class.getDeclaredField(field)));
		return Collections.unmodifiableCollection(sortedCards);
	}
	
	public Map<String, ReferenceDeck> getDecks() {
		return Collections.unmodifiableMap(decks);
	}
	
	public List<Card> getMissingCards(StandaloneDeck deckToCheck) {
		List<Card> missingCards = new ArrayList<Card>();
		for (Card card : deckToCheck.cards) {
			Integer hash = card.hashCode();
			if (cards.containsKey(hash)) {
				if (cards.get(hash).count < card.count) {
					Card missingCard = card.clone();
					missingCard.count = card.count - cards.get(hash).count;
					missingCards.add(missingCard);
				}
			} else {
				missingCards.add(card);
			}
		}
		return missingCards;
	}
	
	public void addStandaloneDeck(StandaloneDeck deckToAdd) {
		List<Card> missingCards = getMissingCards(deckToAdd);
		if (missingCards.size() <= 0) {
			decks.put(deckToAdd.name, new ReferenceDeck(deckToAdd));
		} else {
			throw new IllegalArgumentException("The cardbase is missing cards to add this deck.");
		}
	}
	
	public StandaloneDeck exportDeck(String deckName) {
		ReferenceDeck referenceDeck = decks.get(deckName);
		
		if (referenceDeck != null) {
			StandaloneDeck standaloneDeck = new StandaloneDeck();
			
			standaloneDeck.name = referenceDeck.name;
			standaloneDeck.plains = referenceDeck.plains;
			standaloneDeck.islands = referenceDeck.islands;
			standaloneDeck.swamps = referenceDeck.swamps;
			standaloneDeck.mountains = referenceDeck.mountains;
			standaloneDeck.forests = referenceDeck.forests;
			
			for (Integer cardHash : referenceDeck.cardReferences.keySet()) {
				Card card = getCardByHash(cardHash);
				if (card != null) {
					// must clone otherwise the original count is affected too
					card = card.clone();
					card.count = referenceDeck.cardReferences.get(cardHash);
					standaloneDeck.cards.add(card);
				} else {
					throw new IllegalArgumentException("Deck refers to card not in cardbase: " + cardHash);
				}
			}

			return standaloneDeck;
		} else {
			throw new IllegalArgumentException("The specified deck does not exist.");
		}
	}
}