001package com.fs.starfarer.api.impl.campaign.plog;
002
003import java.io.UnsupportedEncodingException;
004import java.util.ArrayList;
005import java.util.Arrays;
006import java.util.Iterator;
007import java.util.LinkedHashMap;
008import java.util.LinkedHashSet;
009import java.util.List;
010import java.util.Map;
011import java.util.zip.DataFormatException;
012import java.util.zip.Deflater;
013import java.util.zip.Inflater;
014
015import com.fs.starfarer.api.Global;
016import com.fs.starfarer.api.campaign.CampaignClockAPI;
017import com.fs.starfarer.api.campaign.CargoAPI;
018import com.fs.starfarer.api.campaign.InteractionDialogAPI;
019import com.fs.starfarer.api.campaign.PlanetAPI;
020import com.fs.starfarer.api.campaign.PlayerMarketTransaction;
021import com.fs.starfarer.api.campaign.PlayerMarketTransaction.ShipSaleInfo;
022import com.fs.starfarer.api.campaign.econ.CommoditySpecAPI;
023import com.fs.starfarer.api.campaign.econ.Industry;
024import com.fs.starfarer.api.campaign.econ.MarketAPI;
025import com.fs.starfarer.api.campaign.listeners.ColonyInteractionListener;
026import com.fs.starfarer.api.campaign.listeners.ColonyPlayerHostileActListener;
027import com.fs.starfarer.api.campaign.listeners.EconomyTickListener;
028import com.fs.starfarer.api.campaign.listeners.PlayerColonizationListener;
029import com.fs.starfarer.api.combat.ShipAPI.HullSize;
030import com.fs.starfarer.api.impl.campaign.rulecmd.salvage.MarketCMD.TempData;
031import com.fs.starfarer.api.impl.campaign.terrain.BaseTiledTerrain;
032import com.fs.starfarer.api.plugins.SurveyPlugin;
033
034public class PlaythroughLog implements EconomyTickListener,
035                                                                           ColonyInteractionListener,
036                                                                           //EconomyUpdateListener,
037                                                                           PlayerColonizationListener,
038                                                                           ColonyPlayerHostileActListener {
039
040        public static final String KEY = "$core_playthroughLog";
041        public static PlaythroughLog getInstance() {
042                Object test = Global.getSector().getMemoryWithoutUpdate().get(KEY);
043                if (test == null) {
044                        test = new PlaythroughLog();
045                        Global.getSector().getMemoryWithoutUpdate().set(KEY, test);
046                        Global.getSector().getListenerManager().addListener(test);
047                }
048                return (PlaythroughLog) test; 
049        }
050        
051        public static class PLIntelUIData {
052                public LinkedHashSet<String> selectedGraphs = new LinkedHashSet<String>();
053        }
054        
055        protected List<PLEntry> entries = new ArrayList<PLEntry>();
056        protected Map<String, PLStat> stats = new LinkedHashMap<String, PLStat>();
057        protected PLIntelUIData uiData = new PLIntelUIData();
058
059        transient protected List<PLSnapshot> data = new ArrayList<PLSnapshot>();
060        protected String saved = "";
061        
062        protected List<SModRecord> smodsInstalled = new ArrayList<SModRecord>();
063        protected List<OfficerSkillGainRecord> officerSkillsLearned = new ArrayList<OfficerSkillGainRecord>();
064        
065        public PlaythroughLog() {
066                //Global.getSector().getEconomy().addUpdateListener(this);
067                initStats();
068        }
069        
070        
071        
072//      // called from the UI when the player visits a colony etc
073//      // take some samples here so that we have a better chance of catching changes in credits/fleet size/etc.
074        public void reportPlayerClosedMarket(MarketAPI market) {
075                reportEconomyTick(-1);
076        }
077
078        public void reportPlayerOpenedMarket(MarketAPI market) {
079                reportEconomyTick(-1);
080        }
081
082        public void reportEconomyTick(int iterIndex) {
083                if (Global.getSector().isInNewGameAdvance() || Global.getSector().getPlayerFleet() == null) return;
084                
085//              if (data.isEmpty()) {
086//                      PLSnapshot snapshot = new PLSnapshot();
087//                      data.add(snapshot);
088//              }
089                
090                for (String key : stats.keySet()) {
091                        PLStat stat = stats.get(key);
092                        stat.accrueValue();
093                }
094        }
095        
096        public void reportEconomyMonthEnd() {
097                if (Global.getSector().isInNewGameAdvance() || Global.getSector().getPlayerFleet() == null) return;
098                
099                takeSnapshot(false);
100        }
101        
102        protected HullSize biggestBought = HullSize.FIGHTER;
103        public void reportPlayerMarketTransaction(PlayerMarketTransaction transaction) {
104                if (biggestBought == null) biggestBought = HullSize.FIGHTER;
105                for (ShipSaleInfo info : transaction.getShipsBought()) {
106                        HullSize size = info.getMember().getHullSpec().getHullSize();
107                        if (size.ordinal() > biggestBought.ordinal()) {
108                                biggestBought = size;
109                                addEntry("Bought " + info.getMember().getVariant().getHullSpec().getNameWithDesignationWithDashClass());
110                        }
111                }
112        }
113        
114        public void reportSaturationBombardmentFinished(InteractionDialogAPI dialog, MarketAPI market, TempData actionData) {
115                addEntry("Saturation-bombarded " + market.getName() + " (" + 
116                                "size " + (market.getSize() + 1) + " " + market.getFaction().getEntityNamePrefix() + " colony)");
117        }
118        
119        public void reportPlayerAbandonedColony(MarketAPI colony) {
120                String extra = "";
121                if (colony.getPlanetEntity() != null) { 
122                        SurveyPlugin plugin = (SurveyPlugin) Global.getSettings().getNewPluginInstance("surveyPlugin");
123                        String cid = plugin.getSurveyDataType(colony.getPlanetEntity());
124                        CommoditySpecAPI pClass = Global.getSettings().getCommoditySpec(cid);
125                        extra = " (" +  pClass.getName() + " " + colony.getPlanetEntity().getTypeNameWithLowerCaseWorld() + ")";
126                }
127                addEntry("Abdandoned size " + colony.getSize() + " colony " + colony.getOnOrAt() + " " + colony.getName() + extra);
128        }
129
130        public void reportPlayerColonizedPlanet(PlanetAPI planet) {
131                SurveyPlugin plugin = (SurveyPlugin) Global.getSettings().getNewPluginInstance("surveyPlugin");
132                String cid = plugin.getSurveyDataType(planet);
133                CommoditySpecAPI pClass = Global.getSettings().getCommoditySpec(cid);
134                addEntry("Established colony on " + planet.getName() + " (" + 
135                                 pClass.getName().replaceAll(" Survey Data", "") + " " + planet.getTypeNameWithLowerCaseWorld() + ")");
136        }
137        
138        
139        public void takeSnapshot(boolean debug) {
140                PLSnapshot snapshot = new PLSnapshot();
141                
142                for (String key : stats.keySet()) {
143                        PLStat stat = stats.get(key);
144                        long value = stat.getValueForAllAccrued();
145                        
146                        if (debug) {
147                                value += (int)((float) Math.random() * 500);
148                                value -= (int)((float) Math.random() * 500);
149                                if (value < 0) value = 0;
150                        }
151                        
152                        snapshot.getData().put(key, value);
153                }
154                
155                // have to add it here otherwise getPrevValue() uses this snapshot not the actual previous one
156                data.add(snapshot);
157        }
158
159        
160        public List<SModRecord> getSModsInstalled() {
161                return smodsInstalled;
162        }
163
164        public void addSModsInstalled(SModRecord record) {
165                smodsInstalled.add(record);
166        }
167        
168        public List<OfficerSkillGainRecord> getOfficerSkillsLearned() {
169                return officerSkillsLearned;
170        }
171
172        public void addOfficerSkillRecord(OfficerSkillGainRecord record) {
173                officerSkillsLearned.add(record);
174        }
175        public void removeOfficerSkillRecord(String personId, String skillId, boolean elite) {
176                Iterator<OfficerSkillGainRecord> iter = officerSkillsLearned.iterator();
177                while (iter.hasNext()) {
178                        OfficerSkillGainRecord record = iter.next();
179                        if (record.personId.equals(personId) && record.skillId.equals(skillId) && record.elite == elite) {
180                                iter.remove();
181                        }
182                }
183        }
184
185
186
187        protected Object readResolve() throws DataFormatException, UnsupportedEncodingException {
188                if (stats == null) {
189                        stats = new LinkedHashMap<String, PLStat>();
190                        initStats();
191                }
192                if (data == null) {
193                        data = new ArrayList<PLSnapshot>();
194                }
195                
196                if (smodsInstalled == null) {
197                        smodsInstalled = new ArrayList<SModRecord>();
198                }
199                if (officerSkillsLearned == null) {
200                        officerSkillsLearned = new ArrayList<OfficerSkillGainRecord>();
201                }
202                
203                
204                byte [] input = BaseTiledTerrain.toByteArray(saved);
205                
206                Inflater decompressor = new Inflater();
207                decompressor.setInput(input);
208                
209                StringBuilder result = new StringBuilder(); 
210                byte [] temp = new byte[100];
211                while (!decompressor.finished()) {
212                        int read = decompressor.inflate(temp);
213                        // this should be OK since the data is base64 encoded so should be ascii not utf8 i.e. no multi-byte chars
214                        result.append(new String(temp, 0, read, "UTF-8"));
215                }
216
217                decompressor.end();
218                
219                saved = result.toString();
220                
221                data.clear();
222                if (saved == null) saved = "";
223                String [] parts = saved.split("\n");
224                for (String p : parts) {
225                        if (p.isEmpty()) continue;
226                        PLSnapshot next = new PLSnapshot(p);
227                        data.add(next);
228                }
229                return this;
230        }
231        
232        protected Object writeReplace() throws UnsupportedEncodingException {
233                saved = "";
234                for (PLSnapshot s : data) {
235                        saved += s.getString() + "\n";
236                }
237                if (!saved.isEmpty()) {
238                        saved = saved.substring(0, saved.length() - 1);
239                }
240
241                Deflater compressor = new Deflater();
242                compressor.setInput(saved.getBytes("UTF-8"));
243                compressor.finish();
244
245                StringBuilder result = new StringBuilder();
246                byte [] temp = new byte[100];
247                while (!compressor.finished()) {
248                        int read = compressor.deflate(temp);
249                        result.append(BaseTiledTerrain.toHexString(Arrays.copyOf(temp, read)));
250                }
251                compressor.end();
252                
253                saved = result.toString();
254                
255                return this;
256        }
257        
258        
259        protected void initStats() {
260                addStat(new PLStatLevel());
261                addStat(new PLStatFleet());
262                addStat(new PLStatCredits());
263                addStat(new PLStatSupplies());
264                addStat(new PLStatFuel());
265                addStat(new PLStatCargo());
266                addStat(new PLStatCrew());
267                addStat(new PLStatMarines());
268                addStat(new PLStatColonies());
269        }
270        
271        public CampaignClockAPI getDateForIndex(int index) {
272                if (index < 0 || index >= data.size()) {
273                        return Global.getSector().getClock();
274                }
275                PLSnapshot s = data.get(index);
276                CampaignClockAPI clock = Global.getSector().getClock().createClock(s.getTimestamp());
277                return clock;
278        }
279        
280        public void addStat(PLStat stat) {
281                stats.put(stat.getId(), stat);
282        }
283
284        public Map<String, PLStat> getStats() {
285                return stats;
286        }
287        
288        public long getPrevValue(String key) {
289                if (data.isEmpty()) return 0;
290                Long val = data.get(data.size() - 1).getData().get(key);
291                if (val == null) return 0;
292                return val;
293        }
294        
295        public List<PLSnapshot> getData() {
296                return data;
297        }
298
299        public List<PLEntry> getEntries() {
300                return entries;
301        }
302        
303        public void addEntry(PLEntry entry) {
304                entries.add(entry);
305        }
306        public void addEntry(String text) {
307                entries.add(new PLTextEntry(text));
308        }
309        public void addEntry(String text, boolean story) {
310                entries.add(new PLTextEntry(text, story));
311        }
312
313        public PLIntelUIData getUIData() {
314                if (uiData == null) {
315                        uiData = new PLIntelUIData();
316                }
317                return uiData;
318        }
319
320        public void reportRaidForValuablesFinishedBeforeCargoShown(InteractionDialogAPI dialog, MarketAPI market, TempData actionData, CargoAPI cargo) {
321        }
322
323        public void reportRaidToDisruptFinished(InteractionDialogAPI dialog, MarketAPI market, TempData actionData, Industry industry) {
324        }
325
326        public void reportTacticalBombardmentFinished(InteractionDialogAPI dialog, MarketAPI market, TempData actionData) {
327        }
328
329        public void reportPlayerOpenedMarketAndCargoUpdated(MarketAPI market) {
330        }
331}
332
333