package com.capgemini.reports.definition.r20report.printing;

import com.capgemini.framework.common.FileTemplate;
import com.capgemini.framework.common.HashMapComparingStringIDs;
import com.capgemini.framework.common.HashMapComparingStringIDs.IdMaker;
import com.capgemini.framework.common.ResourceHelper;
import com.capgemini.framework.common.StringHelper;
import com.capgemini.reports.changeobjects.change.TcChangeRequestRevision;
import com.capgemini.reports.definition.r20report.ExecutionR20.ReportDataR20;
import com.capgemini.reports.exceptions.FileLoadingException;
import com.capgemini.reports.exceptions.InvalidInputException;
import com.capgemini.reports.exceptions.ReportPrintException;
import com.capgemini.reports.jasper.compilers.JasperCompiler;
import com.capgemini.reports.printing.PrintParameters;
import com.capgemini.reports.snecmaobjects.generic.AbstractGeneric;
import com.capgemini.reports.templates.Alignment;
import com.capgemini.reports.templates.CellFormat;
import com.capgemini.reports.templates.DynamicGridTemplateBuilder;
import lombok.extern.slf4j.Slf4j;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JasperReport;

import java.util.*;
import java.util.Map.Entry;

/**
 * Generates a template for the page 3 of the R20 report.<br>
 * Adds a number of Dynamic tables as sub-reports to this page.
 */
@Slf4j
public class Page3TemplateGenerator extends FileTemplate {

    private static final String NO_GENERIC_FOUND = "(Aucun générique n'a pu être trouvé)";

    private static final String KEY_COLUMN = "COL";
    private static final String ROW_HEADER_KEY = "ROW_HEADER";

    private static final String TEMPLATE_PAGE3 = "/Jasper/template/report_r20/page3/report_r20_p3.jrxml";
    private static final String LINE1 = "\n        <subreport>";
    private static final String LINE2 = "\n            <reportElement positionType=\"Float\" x=\"";
    private static final String LINE2_P3 = "\" y=\"";
    private static final String LINE2_P4 = "\" width=\"800\" height=\"20\"/>";
    private static final String LINE3 = "\n            <dataSourceExpression><![CDATA[new net.sf.jasperreports.engine.data.JRMapCollectionDataSource((Collection<Map<String,?>>)$P{CROSS_TABLE}.get(\"DATA_";
    private static final String LINE3_P2 = "\"))]]></dataSourceExpression>";
    private static final String LINE4 = "\n            <subreportExpression><![CDATA[$P{CROSS_TABLE}.get(\"TEMPLATE_";
    private static final String LINE4_P2 = "\")]]></subreportExpression>";
    private static final String LINE5 = "\n        </subreport>";

    private static final String DATA_KEY_PREFIX = "DATA_SUBREPORT";
    private static final String TEMPLATE_KEY_PREFIX = "TEMPLATE_SUBREPORT";
    private static final String CROSS_TABLE = "CROSS_TABLE";

    private static final int ONE_LINE_HEIGHT_PX = 13;
    private static final int TWO_LINE_HEIGHT_PX = 28;
    private static final int COLUMN_WIDTH_PX = 80; // 60
    private static final int FIRST_COLUMN_WIDTH_PX = 160; // 200

    private static final String GREY_BACKGROUND = "#E3E3E3";
    private static final int COLUMNS_PER_PAGE = 8; // 10
    private static final int HEIGHT = 20;
    /**
     * Vertical spacing between consecutive tables.
     */
    private static final int SPACING = 10;

    private static final String SANS = "Sans";
    private static final String AVEC = "Avec";
    private static final String EMPTY_STRING = "";
    /**
     * This idMaker says two generics are equal if they have the same ID (it igniores their revisions).
     */
    private static final IdMaker<AbstractGeneric> GENERIC_ID_MAKER = new IdMaker<AbstractGeneric>() {
        private static final long serialVersionUID = 6329281427084937878L;

        @Override
        public String makeId(final AbstractGeneric gen) {
            return gen.getItemId();
        }
    };
    @SuppressWarnings({"unchecked", "rawtypes"})
    private static final Class<Map<TcChangeRequestRevision, String>> CLASS_MAP_CHANGE_TO_STRING = (Class) Map.class;
    /**
     * Number of 'Data' columns to generate.
     */
    private final int totalColumnNumber;
    /**
     * Number of lines (show the generic objects).
     */
    private final int totalLines;
    /**
     * Number of tables to generate .
     */
    private final int numSubGrid;
    private final String page3RawTemplateAsString;
    private final String ligneProduitApres;
    private final List<String> modNames;
    private final List<String> contentKeys;
    private final List<String> lineNames;
    /**
     * Columns, then line: gridData[col][line].
     */
    private final String[][] gridData;

    /**
     * Creates a template Generator. <br>
     * A table may only contain 1 header column, an no more than 10 data columns. Everytime a column is added beyond, a new table is created for it.
     *
     * @param data will allow to prepare data for the report
     * @throws FileLoadingException if unable yo load page 3 template
     */
    Page3TemplateGenerator(final ReportDataR20 data) throws FileLoadingException {
        super();
        this.totalColumnNumber = data.getTotalChangeRequest().size(); // At least 1 column, otherwise we'd findAll nullpointers
        this.totalLines = data.getTotalGenerics().size();
        this.numSubGrid = 1 + totalColumnNumber / COLUMNS_PER_PAGE;
        this.page3RawTemplateAsString = ResourceHelper.getResourceContent(TEMPLATE_PAGE3);
        this.ligneProduitApres = data.getProduct().getProductLine();
        this.contentKeys = setupContentKeys(totalColumnNumber);
        this.modNames = setupModNames(data);
        this.lineNames = setupLineNames(data.getTotalGenerics());
        this.gridData = setupGridData(data, this.modNames);
    }

    private static List<String> setupContentKeys(final int numCol) {
        List<String> list = new ArrayList<>();
        for (int num = 0; num < numCol; num++) {
            list.add(KEY_COLUMN + num);
        }
        return list;
    }

    /**
     * This methods sets up the name of the columns (the name of the generics).
     *
     * @param allGenerics all the generics that will be in the table
     * @return a List of strings
     */
    private static List<String> setupLineNames(final Set<AbstractGeneric> allGenerics) {
        List<String> list = new ArrayList<>();
        for (AbstractGeneric generic : allGenerics) {
            list.add(generic.getName());
        }
        return list;
    }

    /**
     * creates a list of all the Modification names.<br>
     */
    private static List<String> setupModNames(final ReportDataR20 data) {
        List<String> list = new ArrayList<>();
        for (TcChangeRequestRevision changeLMR : data.getTotalChangeRequestLMR()) {
            list.add("Non Application<br>" + changeLMR.getItemId());
        }
        for (TcChangeRequestRevision changeNotLMR : data.getTotalChangeRequestNotLMR()) {
            list.add("Application<br>" + changeNotLMR.getItemId());
        }
        return list;
    }

    /**
     * Columns BEFORE lines!.
     */
    private static String[][] setupGridData(final ReportDataR20 data, final List<String> modNames) {
        int numCol = data.getTotalChangeRequest().size();
        int numLine = data.getTotalGenerics().size();

        // Initialize the array
        String[][] grid = new String[numCol][numLine];

        if (!modNames.isEmpty()) {
            Map<AbstractGeneric, Map<TcChangeRequestRevision, String>> dataMap =
                    new HashMapComparingStringIDs<>(GENERIC_ID_MAKER, AbstractGeneric.class,
                            CLASS_MAP_CHANGE_TO_STRING);
            // Put the generics in
            putGenericsInDataMap(dataMap, data.getTotalGenericsAfterWithRupture(), AVEC);
            putGenericsInDataMap(dataMap, data.getTotalGenericsBeforeLMRWithRupture(), AVEC);
            putGenericsInDataMap(dataMap, data.getTotalGenericsAfterWithoutRupture(), SANS);
            putGenericsInDataMap(dataMap, data.getTotalGenericsBeforeLMRWithoutRupture(), SANS);

            // Use the map to buid the array
            int line = 0;
            for (AbstractGeneric gen : data.getTotalGenerics()) {
                int col = 0;
                for (TcChangeRequestRevision mod : data.getTotalChangeRequest()) {
                    if (dataMap.containsKey(gen)) {
                        if (dataMap.get(gen).containsKey(mod)) {
                            grid[col][line] = dataMap.get(gen).get(mod);
                        } else {
                            grid[col][line] = EMPTY_STRING;
                        }
                    } else {
                        grid[col][line] = EMPTY_STRING;
                    }
                    col++;
                }
                line++;
            }
        }
        return grid;
    }

    private static void putGenericsInDataMap(final Map<AbstractGeneric, Map<TcChangeRequestRevision, String>> data,
                                             final Map<AbstractGeneric, Set<TcChangeRequestRevision>> map, final String withOrWithout) {
        for (Entry<AbstractGeneric, Set<TcChangeRequestRevision>> genEntry : map.entrySet()) {
            if (!data.containsKey(genEntry.getKey())) {
                data.put(genEntry.getKey(), new HashMap<TcChangeRequestRevision, String>());
            }
            for (TcChangeRequestRevision changeRequest : genEntry.getValue()) {
                data.get(genEntry.getKey()).put(changeRequest, withOrWithout);
            }
        }
    }

    static String subReportGrid(final int positionX, final int positionY, final String subReportKey) {
        return StringHelper.concatToString(LINE1, LINE2, String.valueOf(positionX), LINE2_P3, String.valueOf(positionY), LINE2_P4, LINE3,
                subReportKey, LINE3_P2, LINE4, subReportKey, LINE4_P2, LINE5);
    }

    /**
     * Generates the page's {@link PrintParameters}.
     *
     * @return the printing instructions, as a {@link PrintParameters} object
     * @throws FileLoadingException  if a template could not be loaded
     * @throws ReportPrintException  if a template could not be compiled
     * @throws InvalidInputException if the table cannot be constructed properly in the template (invalid columns/rows count)
     */
    PrintParameters generatePrintParameters() throws ReportPrintException, FileLoadingException, InvalidInputException {
        PrintParameters page3 = PrintParameters.createSubReport(this.generateCode());

        // For each Table...
        Map<String, Object> crossTable = new HashMap<>();
        for (int tableNum = 0; tableNum < numSubGrid; tableNum++) {
            int columnsDone = tableNum * COLUMNS_PER_PAGE;
            int columnsLeft = totalColumnNumber - columnsDone;
            int numColInThisTable = Math.min(columnsLeft, COLUMNS_PER_PAGE); // Total number of columns in this table, max is COLUMNS_PER_PAGE
            String dataKey = DATA_KEY_PREFIX + tableNum;
            String templateKey = TEMPLATE_KEY_PREFIX + tableNum;
            String templateString = setupGridTemplate(columnsDone, columnsDone + numColInThisTable);
            JasperReport compiledTemplate;
            try {
                compiledTemplate = JasperCompiler.compile(templateString);
            } catch (JRException e) {
                throw new ReportPrintException("Could not compile the sub-table template", e);
            }

            // Copy all the lines...
            List<Map<String, ?>> tableData = new ArrayList<>();
            if (gridData.length > 0) {
                for (int lineNum = 0; lineNum < gridData[0].length; lineNum++) {
                    Map<String, Object> line = new HashMap<>();
                    line.put(ROW_HEADER_KEY, lineNames.get(lineNum));
                    // ... of each columns of THIS sub-table
                    for (int tableColNum = 0; tableColNum < numColInThisTable; tableColNum++) { // Index of the working column in the sub-table
                        int gridColNum = tableColNum + columnsDone; // Index of the working column in the main data grid
                        String contentKey = contentKeys.get(gridColNum);
                        String contentVal = gridData[gridColNum][lineNum];
                        line.put(contentKey, contentVal);
                    }
                    tableData.add(line); // One full line is generated
                }
            }
            // If there was nothing in the table, put a fake line to make it show up anyway
            if (tableData.isEmpty()) {
                // If there was some lines (Generic objects to show)
                if (totalLines > 0) {
                    for (int lineNum = 0; lineNum < totalLines; lineNum++) {
                        Map<String, Object> line = new HashMap<>();
                        line.put(ROW_HEADER_KEY, lineNames.get(lineNum));
                        tableData.add(line); // One full line is generated
                    }
                }
                // Otherwise
                else {
                    Map<String, Object> line = new HashMap<>();
                    line.put(ROW_HEADER_KEY, NO_GENERIC_FOUND);
                    tableData.add(line); // One full line is generated
                }
            }
            crossTable.put(templateKey, compiledTemplate);
            crossTable.put(dataKey, tableData);
        }
        page3.addReportParameter(CROSS_TABLE, crossTable);
        page3.addReportData(setupEmptyData());
        return page3;
    }

    private String setupGridTemplate(final int startcol, final int endCol) throws FileLoadingException, InvalidInputException {
        // Setup each column's properties: width, headers and content keys (first column is different)
        List<Integer> widths = new ArrayList<>();
        List<String> keys = new ArrayList<>();
        List<String> headers = new ArrayList<>();
        List<String> firstLine = new ArrayList<>();
        List<Boolean> nothingIsDynamic = new ArrayList<>();
        List<CellFormat> headerFormats = new ArrayList<>();
        List<CellFormat> contentsformats = new ArrayList<>();
        List<Boolean> allIsStretchingwithOverflow = new ArrayList<>();
        widths.add(FIRST_COLUMN_WIDTH_PX);
        keys.add(ROW_HEADER_KEY);
        headers.add("Modifications Techniques");
        firstLine.add(ligneProduitApres);
        nothingIsDynamic.add(false);
        headerFormats.add(new CellFormat().setColor(GREY_BACKGROUND)); // Keep default alignment (left)
        contentsformats.add(new CellFormat()); // Keep default alignment (left)
        allIsStretchingwithOverflow.add(true);
        for (int col = startcol; col < endCol; col++) {
            widths.add(COLUMN_WIDTH_PX);
            keys.add(contentKeys.get(col));
            headers.add(modNames.get(col));
            firstLine.add(EMPTY_STRING); // Empty because only first column has data: "Modifications Techniques"
            nothingIsDynamic.add(false);
            headerFormats.add(new CellFormat().setColor(GREY_BACKGROUND).setAlignment(Alignment.CENTER));
            contentsformats.add(new CellFormat().setAlignment(Alignment.CENTER));
            allIsStretchingwithOverflow.add(true);
        }
        DynamicGridTemplateBuilder template =
                new DynamicGridTemplateBuilder(widths, keys, contentsformats, allIsStretchingwithOverflow, ONE_LINE_HEIGHT_PX);
        template.addHeaderLine(ONE_LINE_HEIGHT_PX, firstLine, nothingIsDynamic, headerFormats, allIsStretchingwithOverflow);
        template.addHeaderLine(TWO_LINE_HEIGHT_PX, headers, nothingIsDynamic, headerFormats, allIsStretchingwithOverflow);
        String generatedTemplate = template.generateCode();
        log.trace(generatedTemplate);
        return generatedTemplate;
    }

    private List<Map<String, ?>> setupEmptyData() {
        List<Map<String, ?>> list = new ArrayList<>();
        list.add(new HashMap<String, Object>());
        return list;
    }

    @Override
    public String generateCode() {
        // Generates the template, containing a number of grid sub-tables
        this.setTemplateContent(page3RawTemplateAsString);
        this.setTagValue("BAND_HEIGHT", String.valueOf(numSubGrid * HEIGHT + SPACING * (numSubGrid - 1)));
        StringBuilder subTables = new StringBuilder();
        for (int num = 0; num < numSubGrid; num++) {
            subTables.append(subReportGrid(0, num * (HEIGHT + SPACING), "SUBREPORT" + num));
        }
        this.setTagValue("SUBTABLES", subTables.toString());
        return super.generateCode();
    }
}
