
package de.ivu.elect.presentation.exchange;

import static java.util.Arrays.asList;
import static java.util.Objects.nonNull;
import static org.apache.commons.lang3.StringUtils.equalsAnyIgnoreCase;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.trimToEmpty;

import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
import javax.inject.Inject;

import org.apache.deltaspike.core.api.scope.ViewAccessScoped;

import de.ivu.elect.business.datei.boundary.CsvDateiConversationSelect;
import de.ivu.elect.business.datei.boundary.CsvDateiEdit;
import de.ivu.elect.business.datei.boundary.CsvDateiExtendedDAO;
import de.ivu.elect.business.datei.boundary.DateiImportConversationSelect;
import de.ivu.elect.business.datei.boundary.DateiImportEdit;
import de.ivu.elect.business.datei.control.CsvDateiLeser;
import de.ivu.elect.business.datei.entity.CsvAnfuehrungszeichen;
import de.ivu.elect.business.datei.entity.CsvDatei;
import de.ivu.elect.business.datei.entity.CsvTrennzeichen;
import de.ivu.elect.business.datei.entity.CsvZeichensatz;
import de.ivu.elect.business.datei.entity.DateiImport;
import de.ivu.elect.business.datei.entity.FileType;
import de.ivu.elect.business.exchange.control.AbstractImporter;
import de.ivu.elect.business.exchange.control.AbstractImporterExporter;
import de.ivu.elect.business.exchange.control.FileFormat;
import de.ivu.elect.business.exchange.entity.ImportStatus;
import de.ivu.elect.jee.faces.FacesContextUtil;
import de.ivu.elect.jee.faces.PreConditionCheck;
import de.ivu.elect.jee.presentation.dynamictable.ColumnModel;
import de.ivu.elect.jee.presentation.redirect.Redirect;
import de.ivu.elect.presentation.hilfe.OnlineHilfeId;

@ViewAccessScoped
@OnlineHilfeId(value = "216", mehrfachVergebenOk = true)
public abstract class AbstractImportFormatFestlegenView<Context> extends AbstractImportExportView<Context> implements Serializable {

  private static final long serialVersionUID = 1L;

  private static final int DEFAULT_PREVIEW_LINES = 20;

  private static final int MAX_ANZAHL_KOPFZEILEN = 25; // ELECT-8640

  @Inject
  private transient CsvDateiConversationSelect csvDateiSelect;

  @Inject
  private transient CsvDateiLeser csvDateiLeser;

  @Inject
  private transient DateiImportConversationSelect dateiImportSelect;

  @Inject
  private transient DateiImportEdit dateiImportEdit;

  @Inject
  private transient CsvDateiEdit csvDateiEdit;

  @Inject
  private transient CsvDateiExtendedDAO csvDateiExtendedDAO;

  private CsvZeichensatz zeichensatz;

  private Long anzahlKopfzeilen;

  private CsvTrennzeichen trennzeichen;

  private CsvAnfuehrungszeichen anfuehrungszeichen;

  private transient List<String[]> liesDatenAlsCsvDateiCache = null;

  private transient List<ColumnModel> columns;

  protected AbstractImportFormatFestlegenView() {
    super();
  }

  @Override
  @Nullable
  @PreConditionCheck
  public String checkPreCondition() {
    String outcome = super.checkPreCondition();

    if (isBlank(outcome)) {
      DateiImport dateiImport = getSelectedEntity();

      if (dateiImport != null && dateiImport.isFormatFestlegenMoeglich()) {
        if (isNonCsvAndOnlyOneFormat(dateiImport)) {
          return formatFestlegen();
        } else {
          if (dateiImport.getImportStatus().isFormatBekannt()) {
            setSelectedFileFormat(dateiImport.getDatei().getFileFormat());
          } else {
            // Wenn nur ein sinnvolles Dateiformat, dann dieses vorauswählen
            if (isSingleSupportedFileFormat(dateiImport)) {
              List<FileFormat> supportedFileFormats = getSupportedFileFormats(dateiImport.getDatei().getFileFormat());

              if (supportedFileFormats.size() == 1) {
                setSelectedFileFormat(supportedFileFormats.iterator().next());
              }
            }
          }

          return null;
        }
      }

      addErrorMessage("datei.import.format.festlegen.navigation.nicht.moeglich");

      return getImportDescription().getImportSearchViewId();
    }

    return outcome;
  }

  @Nullable
  @Override
  protected FileType getKnownFileType() {
    DateiImport dateiImport = getSelectedEntity();
    if (dateiImport != null && dateiImport.getDatei() != null && dateiImport.getDatei().getOriginalDateiname() != null) {

      FileType fileType = FileType.fromFilename(dateiImport.getDatei().getOriginalDateiname());
      if (fileType != null && !FileType.ALL.equals(fileType)) {
        return fileType;
      }
    }
    return null;
  }

  @Override
  protected ExchangeDescription getExchangeDescription() {
    return getImportDescription();
  }

  protected abstract ImportDescription getImportDescription();

  protected abstract AbstractImporter< ? , Context, ? > getImporter();

  private void versucheCsvEinstellungenZuBestimmen() {
    try {
      if (getSelectedEntity().getDatei().isCsvDatei()) {
        // default ist UTF-8
        setZeichensatz(CsvZeichensatz.UTF_8); // versuche ISO

        if (!umlauteInVorschauVorhanden()) {
          setZeichensatz(CsvZeichensatz.ISO_8859_1); // versuche ISO

          if (!umlauteInVorschauVorhanden()) {
            setZeichensatz(CsvZeichensatz.UTF_8); // wenn auch keine Umlaute in ISO wieder zurück auf UTF-8
          }
        }

        Long anzahlKopfzeilenBestimmt = getAnzahlKopfzeilen();

        for (int zeileIndexOneBased = 1; zeileIndexOneBased <= 5; zeileIndexOneBased++) {
          if (headerEntsprichtXterZeile(zeileIndexOneBased)) {
            anzahlKopfzeilenBestimmt = (long) zeileIndexOneBased;

            break;
          }
        }

        setAnzahlKopfzeilen(anzahlKopfzeilenBestimmt);
      }
    } catch (Exception ignored) {
      // ignorieren, war ja nur ein Versuch...
    } finally {
      resetLiesDatenAlsCsvDateiCache();
    }
  }

  private boolean headerEntsprichtXterZeile(int zeileIndexOneBased) {
    setAnzahlKopfzeilen(0L); // ELECT-7949, vor Aufruf von zurücksetzen!!!

    int zeileIndexZeroBased = zeileIndexOneBased - 1;

    List<String> headers = getColumnHeaders(Collections.emptyList());
    List<String[]> zeilen = getPreviewData();

    if (!headers.isEmpty() && zeileIndexZeroBased >= 0 && zeileIndexZeroBased < zeilen.size()) {
      String[] zeile = zeilen.get(zeileIndexZeroBased);

      if (zeile != null && zeile.length > 0) {
        for (int i = 0; i < zeile.length && i < headers.size(); i++) {
          String expected = headers.get(i);

          if (expected == null) {
            return false;
          }

          String value = zeile[i];

          if (value == null) {
            return false;
          }

          if (!equalsAnyIgnoreCase(trimToEmpty(expected), trimToEmpty(value))) {
            return false;
          }
        }

        return true;
      }
    }

    return false;
  }

  private boolean umlauteInVorschauVorhanden() {
    String umlaute = "üäöÜÄÖß";

    char[] chars = umlaute.toCharArray();

    for (String[] strings : getPreviewData()) {
      for (String string : strings) {
        if (string != null) {
          for (char c : chars) {
            if (string.indexOf(c) > -1) {
              return true;
            }
          }
        }
      }
    }

    return false;
  }

  @Override
  @PostConstruct
  public void initFileFormat() {
    super.initFileFormat();

    CsvDatei csvDatei = getCsvDatei();

    if (csvDatei != null) {
      zeichensatz = csvDatei.getZeichensatz();
      anzahlKopfzeilen = csvDatei.getAnzahlKopfzeilen();
      trennzeichen = csvDatei.getTrennzeichen();
      anfuehrungszeichen = csvDatei.getAnfuehrungszeichen();
    }
  }

  @Redirect
  public String formatFestlegen() {
    DateiImport selectedDatei = getSelectedEntity();
    uebernehmen();
    selectedDatei.setImportStatus(ImportStatus.FORMAT_FESTGELEGT);
    dateiImportEdit.save(selectedDatei);
    // Keine Meldung, da man als Anwender denkt, der Import ist bereits erfolgt und war erfolgreich
    //    addInfoMessage("datei.import.edit.action.format.festgelegt.success.message", selectedDatei.getDatei()
    //        .getDateiname());

    return getImportDescription().getImportBestaetigenViewId();
  }

  @Redirect
  public String cancel() {
    return getImportDescription().getImportSearchViewId();
  }

  @Redirect
  public String uebernehmen() {
    CsvDatei csvDatei = getSelectedCsvDateiMitUebernommenenWerten();
    csvDateiEdit.save(csvDatei);
    return FacesContextUtil.getCurrentViewId();
  }

  public DateiImport getSelectedEntity() {
    return dateiImportSelect.getEntity();
  }

  public CsvDatei getCsvDatei() {
    CsvDatei csvDatei = csvDateiSelect.getEntity();
    if (csvDatei == null) {
      if (dateiImportSelect.isSelected()) {
        csvDatei = csvDateiExtendedDAO.findByPrimaryKey(getSelectedEntity().getDatei().getPk());
        csvDateiSelect.setEntity(csvDatei);
      }
    }
    return csvDatei;
  }

  private CsvDatei getSelectedCsvDateiMitUebernommenenWerten() {
    CsvDatei csvDatei = csvDateiSelect.getEntity();
    csvDatei.setAnfuehrungszeichen(getAnfuehrungszeichen());
    csvDatei.setAnzahlKopfzeilen(getAnzahlKopfzeilen());
    csvDatei.setTrennzeichen(getTrennzeichen());
    csvDatei.setZeichensatz(getZeichensatz());

    return csvDatei;
  }

  public List<ColumnModel> getColumns() {
    if (columns == null) {
      FileFormat fileFormat = getSelectedEntity().getDatei().getFileFormat();

      if (fileFormat.getFileType().isCsvFile()) {
        columns = createColumns(fileFormat, getContext(), getSelectedCsvDateiMitUebernommenenWerten());
      } else {
        columns = new ArrayList<>();
      }
    }

    return columns;
  }

  private int getIndexLetzteKopfzeile(CsvDatei csvDatei) {
    if (csvDatei.getAnzahlKopfzeilen() > 0) {
      return (int) (csvDatei.getAnzahlKopfzeilen() - 1);
    }

    return 0;
  }

  public List<String[]> getPreviewData() {
    CsvDatei csvDatei = getSelectedCsvDateiMitUebernommenenWerten();

    List<String[]> previewData = liesDatenAlsCsvDatei(csvDatei);

    if (previewData.isEmpty()) {
      return previewData;
    }

    previewData = previewData.subList(csvDatei.getAnzahlKopfzeilen().intValue(), previewData.size());

    return previewData;
  }

  private List<String[]> liesDatenAlsCsvDatei(CsvDatei currentCsvDatei) {
    if (liesDatenAlsCsvDateiCache == null) {
      try {
        liesDatenAlsCsvDateiCache = csvDateiLeser.liesDatenAlsCsvDatei(currentCsvDatei, DEFAULT_PREVIEW_LINES + currentCsvDatei.getAnzahlKopfzeilen().intValue());
      } catch (IOException e) {
        getMessages().addThrowableMessageAndLog("datei.import.error.csv.lesen", e, e.getMessage());

        try { // Cf. ELECT-2392 bzw. ELECTMUC-332 - eine Datei mit Zeilenumbrüchen in Feldern kann importiert werden, aber die Vorschau funktioniert nicht
          liesDatenAlsCsvDateiCache = csvDateiLeser.liesDatenAlsZeilen(currentCsvDatei, DEFAULT_PREVIEW_LINES + currentCsvDatei.getAnzahlKopfzeilen().intValue());
        } catch (IOException e2) {
          getMessages().addThrowableMessageAndLog("datei.import.error.csv.lesen", e2, e2.getMessage());

          liesDatenAlsCsvDateiCache = Collections.emptyList();
        }
      }
    }

    return liesDatenAlsCsvDateiCache;
  }

  protected void resetLiesDatenAlsCsvDateiCache() {
    liesDatenAlsCsvDateiCache = null;
    columns = null;
  }

  protected List<ColumnModel> createColumns(FileFormat fileFormat, Context context, CsvDatei csvDatei) {
    List<ColumnModel> result = new ArrayList<>();

    List<String[]> previewData = liesDatenAlsCsvDatei(csvDatei);

    if (!previewData.isEmpty()) {
      result = createColumns(previewData.get(getIndexLetzteKopfzeile(csvDatei)));
      result = getImporter().sortCsvColumns(fileFormat, context, result);
    }

    return result;
  }

  private List<ColumnModel> createColumns(String[] columnsRow) {
    List<ColumnModel> result = new ArrayList<>();

    List<String> columnHeaders = getColumnHeaders(asList(columnsRow));

    for (int i = 0; i < columnsRow.length; i++) {
      String header = columnHeaders != null && columnHeaders.size() > i ? columnHeaders.get(i) : null;

      result.add(new ColumnModel(header, String.valueOf(i)));
    }

    return result;
  }

  private List<String> getColumnHeaders(List<String> headerInDatei) {
    return getImporter().getCsvColumnHeaders(getSelectedEntity().getDatei().getFileFormat(), getContext(), headerInDatei);
  }

  public List<CsvTrennzeichen> getTrennzeichenValues() {
    return Arrays.asList(CsvTrennzeichen.values());
  }

  public List<CsvAnfuehrungszeichen> getAnfuehrungszeichenValues() {
    return Arrays.asList(CsvAnfuehrungszeichen.values());
  }

  public List<CsvZeichensatz> getZeichensatzValues() {
    return Arrays.asList(CsvZeichensatz.values());
  }

  public List<Long> getAnzahlKopfzeilenValues() {
    List<Long> result = new ArrayList<>();
    for (int i = 0; i <= MAX_ANZAHL_KOPFZEILEN; i++) {
      result.add((long) i);
    }

    return result;
  }

  @Override
  protected AbstractImporterExporter<Context> getImporterExporter() {
    return getImporter();
  }

  @Override
  public void setSelectedFileFormat(FileFormat fileFormat) {
    FileFormat fileFormatOverridden = getSelectedFileFormat();

    super.setSelectedFileFormat(fileFormat);

    DateiImport dateiImport = getSelectedEntity();

    if (nonNull(dateiImport)) {
      dateiImport.getDatei().setFileFormat(fileFormat);

      if (isCsvDatei(dateiImport) && !dateiImport.getImportStatus().isFormatBekannt() && !Objects.equals(fileFormat, fileFormatOverridden)) {
        versucheCsvEinstellungenZuBestimmen();
      }
    }
  }

  public CsvZeichensatz getZeichensatz() {
    return zeichensatz;
  }

  public void setZeichensatz(CsvZeichensatz zeichensatz) {
    this.zeichensatz = zeichensatz;

    resetLiesDatenAlsCsvDateiCache();
  }

  public Long getAnzahlKopfzeilen() {
    return anzahlKopfzeilen;
  }

  public void setAnzahlKopfzeilen(Long anzahlKopfzeilen) {
    this.anzahlKopfzeilen = anzahlKopfzeilen;

    resetLiesDatenAlsCsvDateiCache();
  }

  public CsvTrennzeichen getTrennzeichen() {
    return trennzeichen;
  }

  public void setTrennzeichen(CsvTrennzeichen trennzeichen) {
    this.trennzeichen = trennzeichen;

    resetLiesDatenAlsCsvDateiCache();
  }

  public CsvAnfuehrungszeichen getAnfuehrungszeichen() {
    return anfuehrungszeichen;
  }

  public void setAnfuehrungszeichen(CsvAnfuehrungszeichen anfuehrungszeichen) {
    this.anfuehrungszeichen = anfuehrungszeichen;

    resetLiesDatenAlsCsvDateiCache();
  }

  public boolean isSingleSupportedFileFormat() {
    return isSingleSupportedFileFormat(getSelectedEntity());
  }

  public String getTooltip(ColumnModel columnModel) {
    return null;
  }
}
