package com.capgemini.reports.templates;

import com.capgemini.framework.common.FileTemplate;
import com.capgemini.framework.common.ResourceHelper;
import com.capgemini.reports.exceptions.FileLoadingException;
import com.capgemini.reports.exceptions.InvalidInputException;

import java.util.ArrayList;
import java.util.List;

/**
 * Dynamically creates a jrxml template for a table with an arbitrary number of columns.
 */
public class DynamicGridTemplateBuilder extends FileTemplate {

  // Keys to replace in the template.jrxml
  private static final String HEADER_HEIGHT = "HEADER_HEIGHT";
  private static final String CONTENT_HEIGHT = "CONTENT_HEIGHT";
  protected static final String TITLE_HEIGHT = "TITLE_HEIGHT";
  private static final String TOTAL_HEIGHT = "TOTAL_HEIGHT";
  private static final String TOTAL_WIDTH = "TOTAL_WIDTH";
  private static final String PARAMETERS = "PARAMETERS";
  private static final String FIELDS = "FIELDS";
  private static final String HEADERS = "HEADERS";
  private static final String CONTENT = "CONTENT";
  protected static final String PROPERTY = "PROPERTY";
  private static final String IS_IGNORE_PAGINATION = "IS_IGNORE_PAGINATION";
  protected static final String STYLE = "STYLE";
  protected static final String TITLE = "TITLE";
  protected static final String NAME_SHEET = "NAME_SHEET";
  protected StringBuilder parametersDeclaration = new StringBuilder();
  protected StringBuilder fieldsDeclaration = new StringBuilder();
  protected StringBuilder contentStyle = new StringBuilder();
  private final StringBuilder headersDeclaration = new StringBuilder();
  private final StringBuilder contentFields = new StringBuilder();
  protected StringBuilder titleDeclaration = new StringBuilder();
  protected StringBuilder propertyDeclaration = new StringBuilder();
  protected String nameSheetDeclaration = "";
  protected final List<Column> columns;
  private final int contentLineHeight;
  private final int numberOfColumns;
  private final int totalGridWidth;
  protected int heigthTitle;
  private int totalHeaderHeight;
  protected static final String BEGIN_STYLE = "\n    <style name=\"";
  protected static final String BEGIN_PARAM = "\n<parameter name=\"";
  protected static final String END_PARAM = "\" class=\"java.lang.String\"/>";
  protected static final String END_VAR = "\" class=\"java.lang.String\">";
  protected static final String END_STYLE = " >";
  protected static final String BEGIN_CONDITIONAL_STYLE = "       <conditionalStyle> \n";
  protected static final String END_CONDITIONAL_STYLE = "\n       </conditionalStyle>";
  protected static final String BEGIN_CONDITIONAL_EXPRESSION = "          <conditionExpression> ";
  protected static final String END_CONDITIONAL_EXPRESSION = "</conditionExpression> \n";
  protected static final String BEGIN_STYLE_MODE = "          <style mode=";
  protected static final String END_STYLE_MODE = "\n          </style>";
  protected static final String END_STYLE_FIRST = "\n    </style>";
  protected static final String MULTIPLE_CONDITIONAL_INCLUSIF = " && ";
  protected static final String MULTIPLE_CONDITIONAL_EXCLUSIF = " || ";
  protected static final String BEGIN_VARIABLE_DATA = "<![CDATA[$F{";
  protected static final String BEGIN_VARIABLE_DATA_FALSE_BOOLEAN = "<![CDATA[!$F{";
  protected static final String BEGIN_VARIABLE_DATA_CONDITION = "<![CDATA[($F{";
  protected static final String BEGIN_VARIABLE_DATA_FALSE_BOOLEAN_CONDITION = "<![CDATA[(!$F{";
  protected static final String BEGIN_VARIABLE_PARAM_DATA = "<![CDATA[$P{";
  protected static final String BEGIN_VARIABLE_DECLARATION = "<![CDATA[$V{";
  protected static final String BEGIN_VARIABLE_FUNCTION = "$F{";
  protected static final String BEGIN_VARIABLE_FUNCTION_FALSE_BOOLEAN = "!$F{";
  protected static final String BEGIN_BOX = "<box>";
  protected static final String TOP_PEN = "<topPen ";
  protected static final String LINE_WIDTH = "lineWidth=\"";
  protected static final String LINE_COLOR = " lineColor= \"";
  protected static final String LEFT_PEN = "<leftPen ";
  protected static final String BOTTOM_PEN = "<bottomPen ";
  protected static final String RIGHT_PEN = "<rightPen ";
  protected static final String END = "/>";
  protected static final String END_BOX = "</box>";
  protected static final String BEGIN_VARIABLE = "<variable name=";
  protected static final String END_VARIABLE = "</variable>";
  protected static final String BEGIN_VARIABLE_EXPRESSION = "<variableExpression>";
  protected static final String END_VARIABLE_EXPRESSION = "</variableExpression>";
  protected final List<String> listElementTitle = new ArrayList<>();

  /**
   * Builds a table template.
   *
   * @param widths              The widths of every columns in this table
   * @param contentKeys         the String keys of each column's content fields
   * @param contentLineHeight   The height of each content rows, in pixels
   * @param formats             (optional) a List of field {@link CellFormat}. If the list is null or empty, no format is specified
   * @param stretchWithOverFlow (optional) a List of field alignments. If the list is null or empty, no stretch will be performed
   * @throws FileLoadingException  if the template could not be loaded
   * @throws InvalidInputException if input list length do not match
   */
  public DynamicGridTemplateBuilder(final List<Integer> widths, final List<String> contentKeys, final List<CellFormat> formats,
                                    final List<Boolean> stretchWithOverFlow, final int contentLineHeight) throws FileLoadingException, InvalidInputException {
    super();
    heigthTitle = 0;
    if (widths == null || contentKeys == null || widths.size() != contentKeys.size()) {
      throw new InvalidInputException("Column size mismatch: widths List must be the same length as contentKeys list");
    }
    if (formats != null && !formats.isEmpty() && formats.size() != widths.size()) {
      throw new InvalidInputException("Alignments specified but do not match the table size");
    }
    this.numberOfColumns = widths.size();
    // Setup each column
    int incrementalWidth = 0;
    this.columns = new ArrayList<>(numberOfColumns);
    this.contentLineHeight = contentLineHeight;
    for (int numCol = 0; numCol < numberOfColumns; numCol++) {
      Integer width = widths.get(numCol);
      CellFormat fieldFormat = (formats == null || formats.isEmpty()) ? null : formats.get(numCol);
      boolean isStretch = (stretchWithOverFlow == null || stretchWithOverFlow.isEmpty()) ? false : stretchWithOverFlow.get(numCol);
      columns.add(new Column(contentKeys.get(numCol), width, incrementalWidth, isStretch, contentLineHeight, fieldFormat));
      incrementalWidth += width;
    }
    totalGridWidth = incrementalWidth;
    this.setTemplateContent(ResourceHelper.getResourceContent("/Jasper/template/dynamic_table/template.jrxml"));
    this.setTagValue(IS_IGNORE_PAGINATION, "false");

  }

  /**
   * Makes this template nover change page (use this to have a single long excel sheet).
   */
  public void ignorePagination() {
    this.setTagValue(IS_IGNORE_PAGINATION, "true");
  }

  /**
   * Adds a header line to this table. The line is added as the last of the header lines, just before the main body.
   *
   * @param heightPx            th eline's height, in pixels
   * @param lineContent         the line's content: Either the static text, the dynamic parameter key, or null if the cell above should be fused with this one
   * @param dynamic             a list of booleans, telling this line if each of the columns above are dynamic (tru) or static (false) or null if the cell above should be fused with this one
   * @param formats             a List of field Formats.
   * @param stretchWithOverFlow (optional) a List of field alignments. If the list is null or empty, no stretch will be performed
   * @throws InvalidInputException when the line's 'content' or 'dynamic' params are not of the same legntth as the number of columns in the report
   */
  public void addHeaderLine(final int heightPx, final List<String> lineContent, final List<Boolean> dynamic, final List<CellFormat> formats,
                            final List<Boolean> stretchWithOverFlow) throws InvalidInputException {
    if (lineContent.size() != numberOfColumns || dynamic.size() != numberOfColumns) {
      throw new InvalidInputException("Line content length does not match the number of columns (" + numberOfColumns + ")");
    }
    CellFormat defaultFormatWithOnlyColor = new CellFormat();
    boolean defaultFormat = formats == null || formats.isEmpty() || formats.size() != numberOfColumns;
    for (int numCol = 0; numCol < numberOfColumns; numCol++) {
      String cellContent = lineContent.get(numCol);
      Column column = columns.get(numCol);
      Boolean isDynamic = dynamic.get(numCol);
      if (cellContent == null || isDynamic == null) {
        column.addToPreviousHeaderCellHeight(heightPx);
      }
      else {
        CellFormat format;
        if (defaultFormat) {
          format = defaultFormatWithOnlyColor;
        }
        else {
          format = formats.get(numCol);
        }
        boolean isStretch = (stretchWithOverFlow == null || stretchWithOverFlow.isEmpty()) ? false : stretchWithOverFlow.get(numCol);
        column.addHeader(cellContent, isDynamic, heightPx, isStretch, format);
      }
    }
    totalHeaderHeight += heightPx;
  }

  /**
   * Adds a header line to this table. The line is added as the last of the header lines, just before the main body.
   *
   * @param heightPx            th eline's height, in pixels
   * @param lineContent         the line's content: Either the static text, the dynamic parameter key, or null if the cell above should be fused with this one
   * @param dynamic             a list of booleans, telling this line if each of the columns above are dynamic (tru) or static (false) or null if the cell above should be fused with this one
   * @param theFormat           the Format for all fields. If null, no format is specified.
   * @param stretchWithOverFlow (optional) a List of field alignments. If the list is null or empty, no stretch will be performed
   * @throws InvalidInputException when the line's 'content' or 'dynamic' params are not of the same legntth as the number of columns in the report
   */
  public void addHeaderLine(final int heightPx, final List<String> lineContent, final List<Boolean> dynamic, final CellFormat theFormat,
                            final List<Boolean> stretchWithOverFlow) throws InvalidInputException {
    if (lineContent.size() != numberOfColumns || dynamic.size() != numberOfColumns) {
      throw new InvalidInputException("Line content length does not match the number of columns (" + numberOfColumns + ")");
    }
    for (int numCol = 0; numCol < numberOfColumns; numCol++) {
      String cellContent = lineContent.get(numCol);
      Column column = columns.get(numCol);
      Boolean isDynamic = dynamic.get(numCol);
      if (cellContent == null || isDynamic == null) {
        column.addToPreviousHeaderCellHeight(heightPx);
      }
      else {
        boolean isStretch = (stretchWithOverFlow == null || stretchWithOverFlow.isEmpty()) ? false : stretchWithOverFlow.get(numCol);
        column.addHeader(cellContent, isDynamic, heightPx, isStretch, theFormat);
      }
    }
    totalHeaderHeight += heightPx;
  }

  @Override
  public String generateCode() {

    for (Column col : columns) {
      parametersDeclaration.append(col.getParametersDeclaration());
      fieldsDeclaration.append(col.getFieldsDeclaration());
      headersDeclaration.append(col.makeHeaders());
      contentFields.append(col.makeContent());
    }
    // Setup the proper tags

    super.setTagValue(PARAMETERS, parametersDeclaration.toString());
    super.setTagValue(FIELDS, fieldsDeclaration.toString());
    super.setTagValue(HEADERS, headersDeclaration.toString());
    super.setTagValue(CONTENT, contentFields.toString());
    super.setTagValue(HEADER_HEIGHT, String.valueOf(totalHeaderHeight));
    super.setTagValue(CONTENT_HEIGHT, String.valueOf(contentLineHeight));
    super.setTagValue(TOTAL_HEIGHT, String.valueOf(totalHeaderHeight + contentLineHeight + heigthTitle));
    super.setTagValue(TOTAL_WIDTH, String.valueOf(totalGridWidth));
    // Generate the content
    return super.generateCode();
  }

}
