package com.oguerreiro.resilient.activities.inputDataImporter.importers;

import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

import org.apache.commons.lang3.StringUtils;
import org.apache.poi.EncryptedDocumentException;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.apache.poi.ss.util.CellRangeAddress;

public class InventoryPOIFileImporter implements Closeable {
  private static final int MAX_TRY_ROWS = 30;

  private InventoryPOIFileImporterCallback callback;
  private InputStream poiInputStream;
  private String password;

  public InventoryPOIFileImporter(InputStream poiInputStream, String password) {
    this.poiInputStream = poiInputStream;
    this.password = password;
  }

  public InventoryPOIFileImporter(File poiFile, String password) throws FileNotFoundException {
    this(new FileInputStream(poiFile), password);
  }

  public void importPOI(File poiFile, InventoryPOIFileImporterCallback callback)
      throws EncryptedDocumentException, IOException {
    this.callback = callback;
    this.loadPOIFile();
  }

  public void importPOI(InventoryPOIFileImporterCallback callback) throws EncryptedDocumentException, IOException {
    this.callback = callback;
    this.loadPOIFile();
  }

  private void loadPOIFile() throws EncryptedDocumentException, IOException {
    this.loadWorkbook();
  }

  private void loadWorkbook() throws EncryptedDocumentException, IOException {

    try (Workbook workbook = WorkbookFactory.create(this.poiInputStream, this.password)) {
      //Loop all sheets			
      for (Sheet sheet : workbook) {
        //Ignore the intro sheet. No data there for me
        if (sheet.getSheetName().toUpperCase().equals("INTRO")) {
          //Read the UO code, and call the ownerCallBack() for validation
          Cell uoCell = sheet.getRow(3).getCell(18);
          Cell yearCell = sheet.getRow(5).getCell(18);
          String uoCode = this.getCellValueString(uoCell);
          Double year = this.getCellValueDouble(yearCell);
          this.callback.validateCallBack(uoCode, year.intValue());
          continue;
        }

        this.loadSheet(sheet);
      }
    }
  }

  private void loadSheet(Sheet sheet) {
    this.printDataHeader(sheet.getSheetName());

    //Loop all rows
    int maxTry = 0;
    boolean isFirst = true;
    boolean ignoreNext = false;
    int lastRowNumber = 0;
    for (Row row : sheet) {
      if ((row.getRowNum() - lastRowNumber) > MAX_TRY_ROWS || maxTry == MAX_TRY_ROWS) {
        //Stop reading. No more data rows
        break;
      }
      lastRowNumber = row.getRowNum();

      if (ignoreNext) {
        ignoreNext = false;
        continue;
      }
      if (this.isRowData(row)) {
        if (isFirst) {
          //The first row with value in CELL("B" | 1), its a HEADER. IGNORE IT. And IGNORE the next row also (merged cell)
          isFirst = false;
          ignoreNext = true;
        } else {
          maxTry = 0;
          if (this.hasData(row)) {
            this.loadRow(row);
          }
        }
      } else {
        isFirst = true;	//Reset. Because the next value will be a HEADER again
        maxTry++;
      }
    }
  }

  private void loadRow(Row row) {
    String varCode = this.getCellValueString(row.getCell(1));
    String varClass = this.removeLineBreaks(this.getCellValueString(row.getCell(3)));
    if (varClass == null || varClass.length() == 0) {
      //Check if this is a merged CELL. Get the value of the merged range
      varClass = this.getMergedCellValue(row.getCell(3));
    }

    String varClassCode = this.processClassCode(varClass);
    String varClassDescription = this.processClassDescription(varClass);

    String varUnit = this.removeLineBreaks(this.getCellValueString(row.getCell(6)));
    String varUnitCode = this.processUnit(varUnit);
    String varValue = this.getCellValueString(row.getCell(7));
    String varSource = this.getCellValueString(row.getCell(8));
    if (varSource == null || varSource.length() == 0) {
      //Check if this is a merged CELL. Get the value of the merged range
      varSource = this.getMergedCellValue(row.getCell(8));
    }
    String varPerson = this.getCellValueString(row.getCell(9));
    if (varPerson == null || varPerson.length() == 0) {
      //Check if this is a merged CELL. Get the value of the merged range
      varPerson = this.getMergedCellValue(row.getCell(9));
    }
    String varNotes = this.getCellValueString(row.getCell(10));
    if (varNotes == null || varNotes.length() == 0) {
      //Check if this is a merged CELL. Get the value of the merged range
      varNotes = this.getMergedCellValue(row.getCell(10));
    }

    this.callback.callback(row.getSheet().getSheetName(), row.getRowNum(), varCode, varClassCode, varClassDescription,
        varUnit, varUnitCode, varValue, varSource, varPerson, varNotes);
    this.printData(varCode, varClassCode, varUnit, varUnitCode, varValue, varSource, varPerson, varNotes);
  }

  private String processClassCode(String varClass) {
    if (varClass == null) {
      return varClass;
    }

    String code = null;
    if (varClass.contains("[") && varClass.contains("]")) {
      //Extract code between [..]
      varClass = varClass.trim();
      String lastChar = varClass.substring(varClass.length() - 1, varClass.length());
      if (!lastChar.equals("]")) {
        //ERROR
      } else {
        int startCodeIndex = varClass.lastIndexOf("[");
        code = varClass.substring(startCodeIndex + 1, varClass.length() - 1);
      }
    }

    return code;
  }

  private String processClassDescription(String varClass) {
    if (varClass == null) {
      return varClass;
    }

    String description = varClass;
    if (varClass.contains("[")) {
      //Extract code between [..]
      int startCodeIndex = varClass.lastIndexOf("[");
      description = varClass.substring(0, startCodeIndex + 1);
    }

    return description;
  }

  private String processUnit(String varUnit) {
    if (varUnit == null) {
      return varUnit;
    }

    String code = null;
    code = StringUtils.stripAccents(varUnit.toUpperCase().trim());	//Uppercase && Trim && Strip accents

    String finalCode = "";
    for (char ch : code.toCharArray()) {
      if (ch < 'A' || ch > 'Z') {
        ch = '_';
      }

      finalCode += ch;
    }

    return finalCode;
  }

  private void printDataHeader(String sheetName) {
    System.out.printf(
        "---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------%n");
    System.out.printf("-- %s --%n", sheetName);
    System.out.printf(
        "---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------%n");
    System.out.printf("| %-10s | %-20s | %-25s | %-25s | %10s | %-35s | %-15s | %-30s |%n", "CODE", "CLASS CODE",
        "UNIT", "UNIT CODE", "VALUE", "SOURCE", "PERSON", "NOTES");
    System.out.printf(
        "---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------%n");
  }

  private void printData(String varCode, String varClass, String varUnit, String varUnitCode, String varValue,
      String varSource, String varPerson, String varnotes) {
    if (varClass == null)
      varClass = "";
    if (varPerson == null)
      varPerson = "";
    if (varSource == null)
      varSource = "";
    if (varnotes == null)
      varnotes = "";
    System.out.printf("| %-10s | %-20s | %-25s | %-25s | %10s | %-35s | %-15s | %-30s |%n", varCode, varClass, varUnit,
        varUnitCode, varValue, varSource, varPerson, varnotes);
  }

  private boolean isRowData(Row row) {
    String varCode = this.getCellValueString(row.getCell(1));

    if (varCode != null && varCode.length() > 0) {
      return true;
    }

    return false;
  }

  private boolean hasData(Row row) {
    String varCode = this.getCellValueString(row.getCell(1));
    String varValue = this.getCellValueString(row.getCell(7));

    //Check if it has data to load
    if (varCode != null && varValue != null && varCode.length() > 0 && varValue.length() > 0) {
      return true;
    }

    return false;
  }

  private String removeLineBreaks(String value) {
    if (value == null)
      return null;

    return value.replace("\n", "").replace("\r", "");
  }

  private String getCellValueString(Cell cell) {
    String value;
    if (cell == null)
      return null;

    try {
      value = cell.getStringCellValue().trim();
    } catch (Exception e) {
      //Try numeric
      double valueDouble = cell.getNumericCellValue();
      value = String.valueOf(valueDouble);
    }

    return value;
  }

  private Double getCellValueDouble(Cell cell) {
    try {
      return cell.getNumericCellValue();
    } catch (Throwable t) {
      return null;
    }
  }

  public String getMergedCellValue(Cell cell) {
    CellRangeAddress range = getMergedRegionForCell(cell);
    if (range != null) {
      Sheet s = cell.getRow().getSheet();
      Cell valueCell = s.getRow(range.getFirstRow()).getCell(range.getFirstColumn());
      return this.getCellValueString(valueCell);
    }

    return null;
  }

  public CellRangeAddress getMergedRegionForCell(Cell c) {
    Sheet s = c.getRow().getSheet();
    for (CellRangeAddress mergedRegion : s.getMergedRegions()) {
      if (mergedRegion.isInRange(c.getRowIndex(), c.getColumnIndex())) {
        // This region contains the cell in question
        return mergedRegion;
      }
    }
    // Not in any
    return null;
  }

  @Override
  public void close() throws IOException {
    // TODO Auto-generated method stub

  }

}
