/*
 * Copyright (C) 2017 John Garner <[email protected]>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.pikatimer.results.reports;

import com.pikatimer.event.Event;
import com.pikatimer.participant.CustomAttribute;
import com.pikatimer.participant.ParticipantDAO;
import com.pikatimer.race.AwardCategory;
import com.pikatimer.race.AwardWinner;
import com.pikatimer.race.Race;
import com.pikatimer.race.RaceDAO;
import com.pikatimer.results.ProcessedResult;
import com.pikatimer.results.RaceReport;
import com.pikatimer.results.RaceReportType;
import static com.pikatimer.results.reports.OverallJSON.getOrdinal;
import com.pikatimer.util.DurationFormatter;
import com.pikatimer.util.Pace;
import java.time.Duration;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringEscapeUtils;

/**
 *
 * @author John Garner <[email protected]>
 */
public class OverallCSV implements RaceReportType{
   
    Race race;
    
        // Defaults
    Boolean showDQ = true;
    Boolean inProgress = false;
    Boolean showSplits = false;
    Boolean showSegments = false;
    Boolean showSegmentPace = false;
    Boolean showDNF = true;
    Boolean showPace = true;
    Boolean showGun = true;
    Boolean showCustomAttributes = false;
    Boolean showAwards = true;
    List<CustomAttribute> customAttributesList = new ArrayList();
    
    Map<String,Boolean> supportedOptions = new HashMap();
    
    public OverallCSV(){
        supportedOptions.put("showDQ", true);
        supportedOptions.put("inProgress", false);
        supportedOptions.put("showSplits", false);
        supportedOptions.put("showSegments", true);
        supportedOptions.put("showSegmentPace", false);
        supportedOptions.put("showCustomAttributes", false);
        supportedOptions.put("showDNF", false);
        supportedOptions.put("showPace", true);
        supportedOptions.put("showGun", true);
        supportedOptions.put("showAwards",true);
    }
    
    @Override
    public void init(Race r) {
        race = r;
    }

    @Override
    public Boolean optionSupport(String feature) {
        return supportedOptions.containsKey(feature);
    }
    
    @Override
    public String process(List<ProcessedResult> prList, RaceReport rr) {
        System.out.println("OverallCSV.process() Called... ");
        String report = new String();
        
        race = rr.getRace();
        
        Event event = Event.getInstance();  // fun with singletons... 
        String eventDate = event.getLocalEventDate().format(DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT));
        String eventName = event.getEventName();
        String rn = "";
        if (RaceDAO.getInstance().listRaces().size() > 1) rn = race.getRaceName();
        String raceName = rn;
        supportedOptions.keySet().forEach(k -> supportedOptions.put(k, rr.getBooleanAttribute(k)));


        showDQ = supportedOptions.get("showDQ");
        inProgress = supportedOptions.get("inProgress");
        showSplits = supportedOptions.get("showSplits");
        showSegments = supportedOptions.get("showSegments");
        showSegmentPace = supportedOptions.get("showSegmentPace");
        showDNF = supportedOptions.get("showDNF");
        showPace = supportedOptions.get("showPace");
        showGun = supportedOptions.get("showGun");
        showCustomAttributes = supportedOptions.get("showCustomAttributes");
        showAwards = supportedOptions.get("showAwards");
        
        
        String dispFormat = race.getStringAttribute("TimeDisplayFormat").replace("[","").replace("}","");
        String roundMode = race.getStringAttribute("TimeRoundingMode");
        Pace pace = Pace.valueOf(race.getStringAttribute("PaceDisplayFormat"));
        
        
        if (showCustomAttributes) customAttributesList= ParticipantDAO.getInstance().getCustomAttributes().stream().filter(a -> { 
            if (rr.getBooleanAttribute(a.getUUID()) != null )
                return rr.getBooleanAttribute(a.getUUID());
            return false;
        }).collect(Collectors.toList());
        if (showCustomAttributes && customAttributesList.isEmpty()) showCustomAttributes = false;
        
        Duration cutoffTime = Duration.ofNanos(race.getRaceCutoff());
        String cutoffTimeString = DurationFormatter.durationToString(cutoffTime, dispFormat, roundMode);
        
        
        final Boolean  penaltiesOrBonuses = prList.stream().anyMatch(s -> (s.getBonus() || s.getPenalty()));

        

        // print the headder
        report += "Event,";
        if (!raceName.isEmpty()) report += "Race,";
        report += "Date,";
        report += "OA#,"; // 4R chars 
        report += "SEX#,"; // 5R chars
        report += "AG#,"; // 5R chars
        report += "BIB,"; // 5R chars for the bib #
        report += "AGE,"; // 4R for the age
        report += "SEX,"; // 4R for the sex
        report += "AG,"; //6L for the AG Group
        report += "Full Name,"; // based on the longest name
        report += "First Name,"; // based on the longest name
        report += "Middle Name,"; // based on the longest name
        report += "Last Name,"; // based on the longest name
        report += "City,"; // 18L for the city
        report += "ST,"; // 4C for the state code
        report += "CO,"; // country
         
        if (showCustomAttributes) {
            for( CustomAttribute a: customAttributesList){
                report += escape(a.getName()) +",";
            }
        }
        // Insert split stuff here
        if (showSplits) {
            for (int i = 2; i < race.splitsProperty().size(); i++) {
                if (!race.splitsProperty().get(i-1).getIgnoreTime()) report += escape(race.splitsProperty().get(i-1).getSplitName()) + ",";
            }
        }
        
        if (showSegments) {
            final StringBuilder chars = new StringBuilder();
            race.raceSegmentsProperty().forEach(seg -> {
                if(seg.getHidden() ) return;
                chars.append(escape(seg.getSegmentName())).append(",");
                if (showSegmentPace&& ! (seg.getUseCustomPace() && Pace.NONE.equals(seg.getCustomPace()))) chars.append(escape(seg.getSegmentName())).append(" Pace,"); 
            });
            report += chars.toString();
        }
        
        if (penaltiesOrBonuses) report += "Adj,";
        // Chip time
        report += "Time";
       
        // gun time
        if (showGun) report += ",Gun";
        // pace
        if (showPace) report += ",Pace"; 
        
        
        StringBuilder awardPrintout = new StringBuilder();
        
        // Map of Bib# that returns a map with a key of AwardCateogry and value of the AwardWinner object. 
        Map<String,Map<AwardCategory,AwardWinner>> awardWinnersByBibMap = new HashMap();
        List<AwardCategory> awardCategoryList = new ArrayList();
        if (showAwards){
            
            Map<AwardCategory,Map<String,List<AwardWinner>>>  awardWinnersMap = race.getAwards().getAwardWinners(prList);
            race.getAwards().awardCategoriesProperty().forEach(ac -> {
                if (!ac.getVisibleOverall()) return;
                awardCategoryList.add(ac);
                awardPrintout.append("," + ac.getName() + " category," + ac.getName() + " place,"+ ac.getName() + " time");
                Map<String,List<AwardWinner>> resultsMap = awardWinnersMap.get(ac);
                List<String> categories = resultsMap.keySet().stream().sorted((k1,k2) -> k1.compareTo(k2)).collect(Collectors.toList());
                categories.forEach(cat -> {
                    String description = (ac.getName() + " -- " +  cat).trim();
                    resultsMap.get(cat).forEach(w -> {
                        if (!awardWinnersByBibMap.containsKey(w.participant.getBib())) awardWinnersByBibMap.put(w.participant.getBib(), new HashMap());
                        awardWinnersByBibMap.get(w.participant.getBib()).put(ac, w);
                                //add(w.awardPlace + getOrdinal(w.awardPlace) + " " + description + " (Time: " + DurationFormatter.durationToString(w.awardTime, dispFormat, roundMode) + ")");
                    });
                });
            });
        }
        if (awardPrintout.length() > 0 ) report += awardPrintout.toString();
        
        report += System.lineSeparator();
        
        final StringBuilder chars = new StringBuilder();
        
        prList.forEach(pr -> {
            
            // if they are a DNF or DQ swap out the placement stats
            Boolean hideTime = false; 
            Boolean dnf = pr.getParticipant().getDNF();
            Boolean dq = pr.getParticipant().getDQ();
            Boolean oco  = false;
            if (dq) hideTime = true;
            
            if (pr.getChipFinish() == null) dnf = true;
                
            if (!showDNF && !inProgress && dnf) return;
            if (!showDQ && dq) return;
            
            if (!cutoffTime.isZero() && ! dnf && !(cutoffTime.compareTo(pr.getChipFinish()) >= 0 
                        || cutoffTimeString.equals(DurationFormatter.durationToString(pr.getChipFinish(), dispFormat, roundMode)))) {
                oco = true;
            }
            
            if (!showDNF && oco) return; 
            
            
            chars.append(eventName).append(",");
            if (!raceName.isEmpty()) chars.append(raceName).append(",");
            chars.append(eventDate).append(",");
            
            if (dq) chars.append("DQ,,,");
            else if (pr.getSplitOCO()){
                chars.append("OCO,,,");
            } else if (inProgress && pr.getChipFinish() == null) {
                chars.append(",,,");
                //hideTime = true;
            } else if (! dnf && ! dq) { 
                if (!oco) {
                    chars.append(pr.getOverall().toString()).append(",");
                    chars.append(pr.getSexPlace().toString()).append(",");
                    chars.append(pr.getAGPlace().toString()).append(","); 
                } else {
                    chars.append("OCO,,,");
                }
            } else {
                chars.append("DNF,,,");
            }
            
            chars.append(pr.getParticipant().getBib()).append(",");
            chars.append(pr.getAge().toString()).append(",");
            chars.append(pr.getSex()).append(",");
            chars.append(pr.getAGCode()).append(",");
            chars.append("\"").append(pr.getParticipant().fullNameProperty().getValueSafe()).append("\"").append(",");
            chars.append("\"").append(pr.getParticipant().getFirstName()).append("\"").append(",");
            chars.append("\"").append(pr.getParticipant().getMiddleName()).append("\"").append(",");
            chars.append("\"").append(pr.getParticipant().getLastName()).append("\"").append(",");
            chars.append("\"").append(pr.getParticipant().getCity()).append("\"").append(",");
            chars.append("\"").append(pr.getParticipant().getState()).append("\"").append(",");
            chars.append("\"").append(pr.getParticipant().getCountry()).append("\"").append(",");


            if (showCustomAttributes) {
                for( CustomAttribute a: customAttributesList){
                    chars.append("\"").append(escape(pr.getParticipant().getCustomAttribute(a.getID()).getValueSafe())).append("\"").append(",");
                }
            }
            // Insert split stuff here 
            if (showSplits) {
            // do stuff
                for (int i = 2; i < race.splitsProperty().size(); i++) {
                    if (!race.splitsProperty().get(i-1).getIgnoreTime()) {
                        if (hideTime) chars.append(",");
                        else chars.append(DurationFormatter.durationToString(pr.getSplit(i), dispFormat, roundMode)).append(",");
                    }
                }
            }
            if (showSegments) {
                Boolean ht = hideTime;
                race.raceSegmentsProperty().forEach(seg -> {
                    if(seg.getHidden() ) return;
                    if (ht) chars.append(",");
                    else chars.append(DurationFormatter.durationToString(pr.getSegmentTime(seg.getID()), dispFormat, roundMode)).append(",");
                    if (showSegmentPace && ! (seg.getUseCustomPace() && Pace.NONE.equals(seg.getCustomPace()))) {
                        if (ht) chars.append(",");
                        else if (pr.getSegmentTime(seg.getID()) != null ) {
                            if (seg.getUseCustomPace()) chars.append(seg.getCustomPace().getPace(seg.getSegmentDistance(), race.getRaceDistanceUnits(), pr.getSegmentTime(seg.getID()))).append(",");
                            else chars.append(pace.getPace(seg.getSegmentDistance(), race.getRaceDistanceUnits(), pr.getSegmentTime(seg.getID()))).append(",");
                        }
                        else chars.append(",");
                    }
                });
            }
            if (dnf || dq) { 
                hideTime = true;
            }
            
            if (penaltiesOrBonuses){
                if (pr.getBonus() || pr.getPenalty()) {
                    if (pr.getBonus()) chars.append("-").append(DurationFormatter.durationToString(pr.getBonusTime(), dispFormat, roundMode));
                    else chars.append("+").append(DurationFormatter.durationToString(pr.getPenaltyTime(), dispFormat, roundMode));
                } 
                chars.append(",");
            }
            // chip time
            if (! hideTime) chars.append(DurationFormatter.durationToString(pr.getChipFinish(), dispFormat, roundMode));
            
            if (showGun) {
                if (! hideTime) chars.append(",").append(DurationFormatter.durationToString(pr.getGunFinish(), dispFormat, roundMode));
                else chars.append(",");
            }
            
            if (showPace){
                if (!hideTime) chars.append(",").append(pace.getPace(race.getRaceDistance().floatValue(), race.getRaceDistanceUnits(), pr.getChipFinish()));
                else chars.append(",");
            }
            
            if (showAwards){
                String bib = pr.getParticipant().getBib();
                awardCategoryList.forEach(ac -> {
                    if (awardWinnersByBibMap.containsKey(bib)) {
                        if (awardWinnersByBibMap.get(bib).containsKey(ac)) {
                            AwardWinner a = awardWinnersByBibMap.get(bib).get(ac);
                            chars.append("," + a.awardTitle + "," + a.awardPlace + "," + DurationFormatter.durationToString(a.awardTime, dispFormat, roundMode));
                        } else { // we didn't win any of these awards
                            chars.append(",,,");
                        }
                    } else { // We didn't win any awards
                        chars.append(",,,");
                    }
                });                
            }
            chars.append(System.lineSeparator());
        
        
        });
            
        
        report += chars.toString();
        

        
        return report;
    }
    
    private String escape(String s){
        return StringEscapeUtils.escapeCsv(s);
    }
    
    
}