001package com.fs.starfarer.api.impl.codex;
002
003import java.io.IOException;
004import java.util.ArrayList;
005import java.util.List;
006import java.util.regex.Matcher;
007import java.util.regex.Pattern;
008
009import java.awt.Color;
010
011import org.json.JSONArray;
012import org.json.JSONException;
013import org.json.JSONObject;
014
015import com.fs.starfarer.api.Global;
016import com.fs.starfarer.api.impl.campaign.intel.BaseIntelPlugin;
017import com.fs.starfarer.api.ui.CustomPanelAPI;
018import com.fs.starfarer.api.ui.LabelAPI;
019import com.fs.starfarer.api.ui.TooltipMakerAPI;
020import com.fs.starfarer.api.ui.UIPanelAPI;
021import com.fs.starfarer.api.util.ListMap;
022import com.fs.starfarer.api.util.Misc;
023
024public class CodexTextEntryLoader {
025
026        public static class ParaData {
027                public int fontSize = 0;
028                public int bulletMode = 0;
029                public Color color = Misc.getTextColor();
030                public String text;
031                public List<String> highlights = new ArrayList<>();
032                public List<Color> colors = new ArrayList<>();
033                public String image;
034                public float imageHeight;
035        }
036        
037        public static class TextEntry extends CodexEntryV2 {
038                public List<Object> data = new ArrayList<>();
039                public String parentId;
040                public TextEntry(String id, String title, String icon) {
041                        super(id, title, icon);
042                        setParam(id);
043                }
044                
045                @Override
046                public void createTitleForList(TooltipMakerAPI info, float width, ListMode mode) {
047                        super.createTitleForList(info, width, mode);
048                }
049
050                @Override
051                public boolean hasCustomDetailPanel() {
052                        return true;
053                }
054
055                @Override
056                public void createCustomDetail(CustomPanelAPI panel, UIPanelAPI relatedEntries, CodexDialogAPI codex) {
057                        Color color = Misc.getBasePlayerColor();
058                        Color dark = Misc.getDarkPlayerColor();
059                        Color h = Misc.getHighlightColor();
060                        Color g = Misc.getGrayColor();
061                        float opad = 10f;
062                        float pad = 3f;
063                        float small = 5f;
064                        
065                        float width = panel.getPosition().getWidth();
066                        
067                        float initPad = 0f;
068                        
069                        float horzBoxPad = 30f;
070                        
071                        // the right width for a tooltip wrapped in a box to fit next to relatedEntries
072                        // 290 is the width of the related entries widget, but it may be null
073                        float tw = width - 290f - opad - horzBoxPad + 10f;
074                        
075                        TooltipMakerAPI text = panel.createUIElement(tw, 0, false);
076                        text.setParaSmallInsignia();
077                
078                        int prevBulletMode = 0;
079                        for (Object o : data) {
080                                if (o instanceof ParaData) {
081                                        ParaData para = (ParaData) o;
082                                        
083                                        TooltipMakerAPI curr = text;
084                                        float outerPad = initPad;
085                                        if (para.image != null) { 
086                                                float height = para.imageHeight;
087                                                if (height <= 0f) height = Global.getSettings().getSprite(para.image).getHeight();
088                                                curr = text.beginImageWithText(para.image, height);
089                                                initPad = 0f;
090                                        }
091                                        
092                                        if (prevBulletMode != 0 && para.bulletMode != 0) { 
093                                                initPad = 0f;
094                                        }
095                                        prevBulletMode = para.bulletMode;
096                                        
097                                        if (para.fontSize == -1) {
098                                                curr.setParaFontDefault();
099                                        } else if (para.fontSize == 0) {
100                                                curr.setParaSmallInsignia();
101                                        } else if (para.fontSize == 1) {
102                                                curr.setParaSmallOrbitron();
103                                        } else if (para.fontSize == 2) {
104                                                curr.setParaOrbitronLarge();
105                                        } else if (para.fontSize == 3) {
106                                                curr.setParaOrbitronVeryLarge();
107                                        }
108                                        
109                                        if (para.bulletMode == 0) {
110                                                curr.setBulletedListMode(null);
111                                        } else if (para.bulletMode == 1) {
112                                                curr.setBulletedListMode(BaseIntelPlugin.INDENT);
113                                        } else if (para.bulletMode == 2) {
114                                                curr.setBulletedListMode(BaseIntelPlugin.BULLET);
115                                        }
116                                        
117                                        LabelAPI label = curr.addPara(para.text, initPad, 
118                                                        para.colors.toArray(new Color[0]), para.highlights.toArray(new String[0]));
119                                        label.setColor(para.color);
120                                        label.setHighlight(para.highlights.toArray(new String[0]));
121                                        label.setHighlightColors(para.colors.toArray(new Color[0]));
122                                        initPad = opad;
123                                        
124                                        prevBulletMode = para.bulletMode;
125                                        
126                                        if (para.image != null) { 
127                                                text.addImageWithText(outerPad);
128                                        }
129                                        
130                                        if (para.fontSize > 0) {
131                                                text.addSpacer(-opad + pad);
132                                        }
133                                }
134                        }
135                        panel.updateUIElementSizeAndMakeItProcessInput(text);
136                        
137                        UIPanelAPI box = panel.wrapTooltipWithBox(text);
138                        panel.addComponent(box).inTL(0f, 0f);
139                        if (relatedEntries != null) {
140                                panel.addComponent(relatedEntries).inTR(0f, 0f);
141                        }
142                        
143                        float height = box.getPosition().getHeight();
144                        if (relatedEntries != null) {
145                                height = Math.max(height, relatedEntries.getPosition().getHeight());
146                        }
147                        panel.getPosition().setSize(width, height);
148                }
149        }
150        
151        
152        public static ListMap<String> LINKS = new ListMap<>();
153        public static void linkRelated() {
154                for (String key : LINKS.keySet()) {
155                        CodexEntryPlugin entry = CodexDataV2.getEntry(key);
156                        for (String key2 : LINKS.getList(key)) {
157                                CodexEntryPlugin other = CodexDataV2.getEntry(key2);
158                                if (entry != null && other != null) {
159                                        CodexDataV2.makeRelated(entry, other);
160                                }
161                        }
162                }
163                LINKS.clear();
164        }
165        
166        public static void loadTextEntries() {
167                try {
168                        String csvName = "data/codex/text_codex_entries.csv";
169                        Global.getLogger(CodexTextEntryLoader.class).info("Loading [" + csvName + "]");
170                        JSONArray rows = Global.getSettings().loadCSV(csvName, true);
171                        
172                        for (int i = 0; i < rows.length(); i++) {
173                                JSONObject row = rows.getJSONObject(i);
174                                String filename = row.getString("filename");
175                                Global.getLogger(CodexTextEntryLoader.class).info("Loading [" + filename + "]");
176                                String contents = Global.getSettings().loadText(filename);
177                                Global.getLogger(CodexTextEntryLoader.class).info("Parsing [" + filename + "]");
178                                parseContents(contents, filename);
179                        }
180                } catch (IOException e) {
181                        throw new RuntimeException(e);
182                } catch (JSONException e) {
183                        throw new RuntimeException(e);
184                }
185        }
186        
187        
188        public static void parseContents(String contents, String filename) {
189                contents = contents.replaceAll("\\r", "").trim();
190                String [] lines = contents.split("\\n");
191
192                Color base = Misc.getBasePlayerColor();
193                Color dark = Misc.getDarkPlayerColor();
194                
195                Color currColor = null;
196                int currFontSize = 0;
197                String currCategory = null;
198                String paraImage = null;
199                int paraImageHeight = 0;
200                
201                TextEntry entry = null;
202                int lineNum = 0;
203                for (String line : lines) {
204                        lineNum++;
205                        if (line.startsWith("#")) continue;
206                        if (line.isBlank()) continue;
207                        line = line.trim();
208                        
209                        if (line.startsWith("CATEGORY ")) {
210                                line = line.replaceFirst("CATEGORY ", "").trim();
211                                Global.getLogger(CodexTextEntryLoader.class).info("Parsing category [" + line + "]");
212                                String [] arr = line.split("\\|");
213                                TextEntry cat = new TextEntry(arr[0], arr[2], CodexDataV2.getIcon(arr[1]));
214                                cat.parentId = arr[3];
215                                cat.setParam(null);
216                                CodexDataV2.ENTRIES.put(cat.getId(), cat);
217                                CodexEntryPlugin parent = CodexDataV2.getEntry(cat.parentId);
218                                if (parent != null) {
219                                        cat.setParent(parent);
220                                        parent.addChild(cat);
221                                }
222                                continue;
223                        }
224                        
225                        if (line.startsWith("COLOR ")) {
226                                line = line.replaceFirst("COLOR ", "").trim();
227                                currColor = getColor(line);
228                                continue;
229                        }
230                        if (line.startsWith("FONT ")) {
231                                line = line.replaceFirst("FONT ", "").trim();
232                                currFontSize = line.toLowerCase().equals("small") ? -1 : 0;
233                                continue;
234                        }
235                        if (line.startsWith("IMAGE ")) {
236                                line = line.replaceFirst("IMAGE ", "").trim();
237                                String [] arr = line.split("\\|");
238                                paraImage = arr[0];
239                                if (arr.length > 1) {
240                                        try {
241                                                paraImageHeight = Integer.parseInt(arr[1]);
242                                        } catch (Throwable t) {
243                                                paraImage = Global.getSettings().getSpriteName(arr[0], arr[1]);
244                                                if (arr.length > 2) {
245                                                        paraImageHeight = Integer.parseInt(arr[2]);
246                                                }
247                                        }
248                                } else {
249                                        paraImageHeight = 0;
250                                }
251                                continue;
252                        }
253                        if (line.startsWith("RESET")) {
254                                currColor = getColor("text");
255                                currFontSize = 0;
256                                continue;
257                        }
258                        if (line.startsWith("CURRENT_CATEGORY ")) {
259                                line = line.replaceFirst("CURRENT_CATEGORY ", "").trim();
260                                currCategory = line;
261                                continue;
262                        }
263                        
264                        if (line.startsWith("BEGIN ")) {
265                                line = line.replaceFirst("BEGIN ", "").trim();
266                                Global.getLogger(CodexTextEntryLoader.class).info("Parsing entry [" + line + "]");
267                                String [] arr = line.split("\\|");
268                                entry = new TextEntry(arr[0], arr[1], null);
269                                entry.parentId = currCategory;
270                                if (arr.length >= 3) entry.parentId = arr[2];
271                                continue;
272                        }
273                        
274                        if (entry == null) {
275                                throw new RuntimeException(String.format("Error parsing [%s] line %s, expected to be inside entry",
276                                                filename, "" + lineNum));
277                        }
278                        
279                        if (line.startsWith("END")) {
280                                if (entry.getIcon() == null) {
281                                        String icon = CodexDataV2.getIcon("generic");
282                                        entry.setIcon(icon);
283                                }
284                                CodexEntryPlugin parent = CodexDataV2.getEntry(entry.parentId);
285                                if (parent != null) {
286                                        entry.setParent(parent);
287                                        parent.addChild(entry);
288                                }
289                                entry = null;
290                                currColor = null;
291                                currFontSize = 0;
292                                continue;
293                        }
294                        
295                        if (line.startsWith("ICON ")) {
296                                line = line.replaceFirst("ICON ", "").trim();
297                                String icon = CodexDataV2.getIcon(line);
298                                entry.setIcon(icon);
299                                continue;
300                        }
301                        
302                        if (line.startsWith("RELATED ")) {
303                                line = line.replaceFirst("RELATED ", "").trim();
304                                String [] ids = line.split("\\|");
305                                for (String id : ids) {
306                                        //entry.addRelatedEntry(id);
307                                        LINKS.add(entry.getId(), id);
308                                }
309                                continue;
310                        }
311                        
312                        
313                        ParaData para = new ParaData();
314                        
315                        Pattern p = Pattern.compile("(?is)^(=+)(.+?)=+$");
316                    Matcher m = p.matcher(line);
317                    if (m.find()) {
318                        line = m.group(2);
319                        para.fontSize = m.group(1).length();
320                        para.color = base;
321                    }
322                    
323                    if (line.startsWith("_ ")) {
324                        line = line.replaceFirst("_ ", "").trim();
325                        para.bulletMode = 1;
326                    } else if (line.startsWith("- ")) {
327                        line = line.replaceFirst("- ", "").trim();
328                        para.bulletMode = 2;
329                    }
330                    
331                    p = Pattern.compile("(?is)\\{\\{(.*?)\\}\\}");
332                    m = p.matcher(line);
333                    
334                    while (m.find()) {
335                        String curr = m.group(1);
336                        String replacement = "";
337                        if (curr.startsWith("color:")) {
338                                curr = curr.replaceFirst("color:", "").trim();
339                                String [] arr = curr.split("\\|");
340                                Color color = getColor(arr[0]);
341                                para.colors.add(color);
342                                para.highlights.add(arr[1]);
343                                replacement = arr[1];
344                        } else if (curr.startsWith("rel:")) {
345                                curr = curr.replaceFirst("rel:", "").trim();
346                                String [] arr = curr.split("\\|");
347                                //entry.addRelatedEntry(arr[0]);
348                                LINKS.add(entry.getId(), arr[0]);
349                                para.colors.add(base);
350                                para.highlights.add(arr[1]);
351                                replacement = arr[1];
352                        } else {
353                                Color color = Misc.getHighlightColor();
354                                para.colors.add(color);
355                                para.highlights.add(curr);
356                                replacement = curr;
357                        }
358                        line = line.replaceFirst(Pattern.quote(m.group(0)), replacement);
359                    }
360                    
361                    para.text = line.replaceAll("%", "%%");
362                    if (currColor != null) {
363                        para.color = currColor;
364                    }
365                    if (para.fontSize == 0) {
366                        para.fontSize = currFontSize;
367                    }
368                    
369                    para.image = paraImage;
370                    para.imageHeight = paraImageHeight;
371                    paraImage = null;
372                    paraImageHeight = 0;
373                    
374                    entry.data.add(para);
375                }
376                
377                if (entry != null) {
378                        throw new RuntimeException(String.format("Error parsing [%s] file ended while still parsing entry", filename));
379                }
380        }
381        
382        public static Color getColor(String str) {
383                if (Global.getSettings().hasDesignTypeColor(str)) {
384                        return Global.getSettings().getDesignTypeColor(str);
385                }
386                
387                Color dark = Misc.getDarkPlayerColor();
388                Color h = Misc.getHighlightColor();
389                Color bad = Misc.getNegativeHighlightColor();
390                Color good = Misc.getPositiveHighlightColor();
391                Color gray = Misc.getGrayColor();
392                Color textColor = Misc.getTextColor();
393                Color player = Misc.getBasePlayerColor();
394                
395                Color color = null;
396                if (str.equals("h")) color = h;
397                else if (str.equals("good")) color = good;
398                else if (str.equals("bad")) color = bad;
399                else if (str.equals("gray")) color = gray;
400                else if (str.equals("grey")) color = gray;
401                else if (str.equals("text")) color = textColor;
402                else if (str.equals("player")) color = player;
403                else if (str.equals("blue")) color = player;
404                else if (str.equals("base")) color = player;
405                else if (str.equals("dark")) color = dark;
406                else color = Global.getSettings().getColor(str);
407                if (color == null) {
408                        throw new RuntimeException(String.format("Parsing error: color [%s] not found", str));
409                }
410                return color;
411        }
412        
413        
414        public static void main(String[] args) {
415                String test = "Testing 100% in text";
416                test = test.replaceAll("%", "%%");
417                System.out.println(String.format(test));
418                
419//              String line = "Flux generated by taking damage on shields is {{color:h|hard}} flux, meaning that it can not dissipate while shields are up. Flux generated by firing weapons is {{color:h|soft}} flux and can dissipate while shields are up.";
420//          Pattern p = Pattern.compile("(?is)\\{\\{(.*?)\\}\\}");
421//          Matcher m = p.matcher(line);
422                
423//          String line = "===A heading===\n";
424//          line = line.trim();
425//          Pattern p = Pattern.compile("(?is)^(=+)(.+?)=+$");
426//          Matcher m = p.matcher(line);
427//              
428//          while (m.find()) {
429//              String curr = m.group(2);
430//              System.out.println("Found: " + curr);
431//          }
432        }
433}
434
435
436
437
438
439
440
441
442
443
444
445
446
447