1 #include "deck_loader.h"
2 
3 #include "carddatabase.h"
4 #include "decklist.h"
5 #include "main.h"
6 
7 #include <QDebug>
8 #include <QFile>
9 #include <QStringList>
10 
11 const QStringList DeckLoader::fileNameFilters = QStringList()
12                                                 << QObject::tr("Common deck formats (*.cod *.dec *.dek *.txt *.mwDeck)")
13                                                 << QObject::tr("All files (*.*)");
14 
DeckLoader()15 DeckLoader::DeckLoader() : DeckList(), lastFileName(QString()), lastFileFormat(CockatriceFormat), lastRemoteDeckId(-1)
16 {
17 }
18 
DeckLoader(const QString & nativeString)19 DeckLoader::DeckLoader(const QString &nativeString)
20     : DeckList(nativeString), lastFileName(QString()), lastFileFormat(CockatriceFormat), lastRemoteDeckId(-1)
21 {
22 }
23 
DeckLoader(const DeckList & other)24 DeckLoader::DeckLoader(const DeckList &other)
25     : DeckList(other), lastFileName(QString()), lastFileFormat(CockatriceFormat), lastRemoteDeckId(-1)
26 {
27 }
28 
DeckLoader(const DeckLoader & other)29 DeckLoader::DeckLoader(const DeckLoader &other)
30     : DeckList(other), lastFileName(other.lastFileName), lastFileFormat(other.lastFileFormat),
31       lastRemoteDeckId(other.lastRemoteDeckId)
32 {
33 }
34 
loadFromFile(const QString & fileName,FileFormat fmt)35 bool DeckLoader::loadFromFile(const QString &fileName, FileFormat fmt)
36 {
37     QFile file(fileName);
38     if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
39         return false;
40     }
41 
42     bool result = false;
43     switch (fmt) {
44         case PlainTextFormat:
45             result = loadFromFile_Plain(&file);
46             break;
47         case CockatriceFormat: {
48             result = loadFromFile_Native(&file);
49             qDebug() << "Loaded from" << fileName << "-" << result;
50             if (!result) {
51                 qDebug() << "Retying as plain format";
52                 file.seek(0);
53                 result = loadFromFile_Plain(&file);
54                 fmt = PlainTextFormat;
55             }
56             break;
57         }
58 
59         default:
60             break;
61     }
62 
63     if (result) {
64         lastFileName = fileName;
65         lastFileFormat = fmt;
66 
67         emit deckLoaded();
68     }
69 
70     qDebug() << "Deck was loaded -" << result;
71     return result;
72 }
73 
loadFromRemote(const QString & nativeString,int remoteDeckId)74 bool DeckLoader::loadFromRemote(const QString &nativeString, int remoteDeckId)
75 {
76     bool result = loadFromString_Native(nativeString);
77     if (result) {
78         lastFileName = QString();
79         lastFileFormat = CockatriceFormat;
80         lastRemoteDeckId = remoteDeckId;
81 
82         emit deckLoaded();
83     }
84     return result;
85 }
86 
saveToFile(const QString & fileName,FileFormat fmt)87 bool DeckLoader::saveToFile(const QString &fileName, FileFormat fmt)
88 {
89     QFile file(fileName);
90     if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
91         return false;
92     }
93 
94     bool result = false;
95     switch (fmt) {
96         case PlainTextFormat:
97             result = saveToFile_Plain(&file);
98             break;
99         case CockatriceFormat:
100             result = saveToFile_Native(&file);
101             break;
102     }
103 
104     if (result) {
105         lastFileName = fileName;
106         lastFileFormat = fmt;
107     }
108     return result;
109 }
110 
111 // This struct is here to support the forEachCard function call, defined in decklist. It
112 // requires a function to be called for each card, and passes an inner node and a card for
113 // each card in the decklist.
114 struct FormatDeckListForExport
115 {
116     // Create refrences for the strings that will be passed in.
117     QString &mainBoardCards;
118     QString &sideBoardCards;
119     // create main operator for struct, allowing the foreachcard to work.
FormatDeckListForExportFormatDeckListForExport120     FormatDeckListForExport(QString &_mainBoardCards, QString &_sideBoardCards)
121         : mainBoardCards(_mainBoardCards), sideBoardCards(_sideBoardCards){};
122 
operator ()FormatDeckListForExport123     void operator()(const InnerDecklistNode *node, const DecklistCardNode *card) const
124     {
125         // Get the card name
126         CardInfoPtr dbCard = db->getCard(card->getName());
127         if (!dbCard || dbCard->getIsToken()) {
128             // If it's a token, we don't care about the card.
129             return;
130         }
131 
132         // Check if it's a sideboard card.
133         if (node->getName() == DECK_ZONE_SIDE) {
134             // Get the number of cards and add the card name
135             sideBoardCards += QString::number(card->getNumber());
136             // Add a space between card num and name
137             sideBoardCards += "%20";
138             // Add card name
139             sideBoardCards += card->getName();
140             // Add a return at the end of the card
141             sideBoardCards += "%0A";
142         } else // If it's a mainboard card, do the same thing, but for the mainboard card string
143         {
144             mainBoardCards += QString::number(card->getNumber());
145             mainBoardCards += "%20";
146             mainBoardCards += card->getName();
147             mainBoardCards += "%0A";
148         }
149     }
150 };
151 
152 // Export deck to decklist function, called to format the deck in a way to be sent to a server
exportDeckToDecklist()153 QString DeckLoader::exportDeckToDecklist()
154 {
155     // Add the base url
156     QString deckString = "https://www.decklist.org/?";
157     // Create two strings to pass to function
158     QString mainBoardCards, sideBoardCards;
159     // Set up the struct to call.
160     FormatDeckListForExport formatDeckListForExport(mainBoardCards, sideBoardCards);
161     // call our struct function for each card in the deck
162     forEachCard(formatDeckListForExport);
163     // Remove the extra return at the end of the last cards
164     mainBoardCards.chop(3);
165     sideBoardCards.chop(3);
166     // if after we've called it for each card, and the strings are empty, we know that
167     // there were no non-token cards in the deck, so show an error message.
168     if ((QString::compare(mainBoardCards, "", Qt::CaseInsensitive) == 0) &&
169         (QString::compare(sideBoardCards, "", Qt::CaseInsensitive) == 0)) {
170         return "";
171     }
172     // return a string with the url for decklist export
173     deckString += "deckmain=" + mainBoardCards + "&deckside=" + sideBoardCards;
174     return deckString;
175 }
176 
getFormatFromName(const QString & fileName)177 DeckLoader::FileFormat DeckLoader::getFormatFromName(const QString &fileName)
178 {
179     if (fileName.endsWith(".cod", Qt::CaseInsensitive)) {
180         return CockatriceFormat;
181     }
182     return PlainTextFormat;
183 }
184 
saveToStream_Plain(QTextStream & out,bool addComments)185 bool DeckLoader::saveToStream_Plain(QTextStream &out, bool addComments)
186 {
187     if (addComments) {
188         saveToStream_DeckHeader(out);
189     }
190 
191     // loop zones
192     for (int i = 0; i < getRoot()->size(); i++) {
193         const auto *zoneNode = dynamic_cast<InnerDecklistNode *>(getRoot()->at(i));
194 
195         saveToStream_DeckZone(out, zoneNode, addComments);
196 
197         // end of zone
198         out << "\n";
199     }
200 
201     return true;
202 }
203 
saveToStream_DeckHeader(QTextStream & out)204 void DeckLoader::saveToStream_DeckHeader(QTextStream &out)
205 {
206     if (!getName().isEmpty()) {
207         out << "// " << getName() << "\n\n";
208     }
209 
210     if (!getComments().isEmpty()) {
211         QStringList commentRows = getComments().split(QRegExp("\n|\r\n|\r"));
212         foreach (QString row, commentRows) {
213             out << "// " << row << "\n";
214         }
215         out << "\n";
216     }
217 }
218 
saveToStream_DeckZone(QTextStream & out,const InnerDecklistNode * zoneNode,bool addComments)219 void DeckLoader::saveToStream_DeckZone(QTextStream &out, const InnerDecklistNode *zoneNode, bool addComments)
220 {
221     // group cards by card type and count the subtotals
222     QMultiMap<QString, DecklistCardNode *> cardsByType;
223     QMap<QString, int> cardTotalByType;
224     int cardTotal = 0;
225 
226     for (int j = 0; j < zoneNode->size(); j++) {
227         auto *card = dynamic_cast<DecklistCardNode *>(zoneNode->at(j));
228 
229         CardInfoPtr info = db->getCard(card->getName());
230         QString cardType = info ? info->getMainCardType() : "unknown";
231 
232         cardsByType.insert(cardType, card);
233 
234         if (cardTotalByType.contains(cardType)) {
235             cardTotalByType[cardType] += card->getNumber();
236         } else {
237             cardTotalByType[cardType] = card->getNumber();
238         }
239 
240         cardTotal += card->getNumber();
241     }
242 
243     if (addComments) {
244         out << "// " << cardTotal << " " << zoneNode->getVisibleName() << "\n";
245     }
246 
247     // print cards to stream
248     foreach (QString cardType, cardsByType.uniqueKeys()) {
249         if (addComments) {
250             out << "// " << cardTotalByType[cardType] << " " << cardType << "\n";
251         }
252 
253         QList<DecklistCardNode *> cards = cardsByType.values(cardType);
254 
255         saveToStream_DeckZoneCards(out, zoneNode, cards, addComments);
256 
257         if (addComments) {
258             out << "\n";
259         }
260     }
261 }
262 
saveToStream_DeckZoneCards(QTextStream & out,const InnerDecklistNode * zoneNode,QList<DecklistCardNode * > cards,bool addComments)263 void DeckLoader::saveToStream_DeckZoneCards(QTextStream &out,
264                                             const InnerDecklistNode *zoneNode,
265                                             QList<DecklistCardNode *> cards,
266                                             bool addComments)
267 {
268     // QMultiMap sorts values in reverse order
269     for (int i = cards.size() - 1; i >= 0; --i) {
270         DecklistCardNode *card = cards[i];
271 
272         if (zoneNode->getName() == DECK_ZONE_SIDE && addComments) {
273             out << "SB: ";
274         }
275 
276         out << card->getNumber() << " " << card->getName() << "\n";
277     }
278 }
279 
getCardZoneFromName(QString cardName,QString currentZoneName)280 QString DeckLoader::getCardZoneFromName(QString cardName, QString currentZoneName)
281 {
282     CardInfoPtr card = db->getCard(cardName);
283 
284     if (card && card->getIsToken()) {
285         return DECK_ZONE_TOKENS;
286     }
287 
288     return currentZoneName;
289 }
290 
getCompleteCardName(const QString cardName) const291 QString DeckLoader::getCompleteCardName(const QString cardName) const
292 {
293     if (db) {
294         CardInfoPtr temp = db->guessCard(cardName);
295         if (temp) {
296             return temp->getName();
297         }
298     }
299 
300     return cardName;
301 }
302