/*
 * Copyright (C) 2000 - 2024 Silverpeas
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * As a special exception to the terms and conditions of version 3.0 of
 * the GPL, you may redistribute this Program in connection with Free/Libre
 * Open Source Software ("FLOSS") applications as described in Silverpeas's
 * FLOSS exception.  You should have received a copy of the text describing
 * the FLOSS exception, and it is also available here:
 * "https://www.silverpeas.org/legal/floss_exception.html"
 *
 * 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

package org.silverpeas.components.kmelia.service;

import org.silverpeas.components.kmelia.model.KmaxRuntimeException;
import org.silverpeas.components.kmelia.model.KmeliaRuntimeException;
import org.silverpeas.core.contribution.content.form.DataRecord;
import org.silverpeas.core.contribution.content.form.Field;
import org.silverpeas.core.contribution.content.form.Form;
import org.silverpeas.core.contribution.content.form.FormException;
import org.silverpeas.core.contribution.content.form.RecordSet;
import org.silverpeas.core.contribution.content.form.Util;
import org.silverpeas.core.contribution.content.form.field.FileField;
import org.silverpeas.core.contribution.publication.model.PublicationDetail;
import org.silverpeas.core.contribution.template.publication.PublicationTemplate;
import org.silverpeas.core.contribution.template.publication.PublicationTemplateException;
import org.silverpeas.core.contribution.template.publication.PublicationTemplateManager;
import org.silverpeas.core.util.MemoizedSupplier;
import org.silverpeas.core.util.file.FileItem;
import org.silverpeas.kernel.util.Pair;
import org.silverpeas.kernel.util.StringUtil;
import org.silverpeas.core.util.file.FileUploadUtil;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * Handles the context of an Xml Form update.
 * <p>
 * It permits to manage some useful caches.
 * </p>
 * @author silveryocha
 */
public class KmeliaXmlFormUpdateContext {

  private final Map<String, PublicationTemplate> templateCache = new HashMap<>();
  private final Map<String, Pair<PublicationTemplate, DataRecord>> dataRecordCache =
      new HashMap<>();
  private final List<FileItem> items;
  private final MemoizedSupplier<String> xmlShortName;
  private final boolean forceUpdatePublication;
  private boolean batchProcessing = false;

  public KmeliaXmlFormUpdateContext(final List<FileItem> items,
      final boolean forceUpdatePublication) {
    this.items = items;
    this.forceUpdatePublication = forceUpdatePublication;
    this.xmlShortName = new MemoizedSupplier<>(
        () -> FileUploadUtil.getParameter(items, "KmeliaPubFormName"));
  }

  public KmeliaXmlFormUpdateContext batchProcessing() {
    this.batchProcessing = true;
    return this;
  }

  public List<FileItem> getItems() {
    return items;
  }

  public boolean isForceUpdatePublication() {
    return forceUpdatePublication;
  }

  public boolean isBatchProcessing() {
    return batchProcessing;
  }

  /**
   * Gets from the context the shot name of the Xml Form.
   * @return a name as string.
   */
  public String getXmlFormShortNameFromItems() {
    return xmlShortName.get();
  }

  /**
   * Gets the list of couples of {@link FileItem} and {@link FileField} about the given
   * publication and language.
   * @param pub a publication.
   * @param language a language.
   * @return a list of couple of {@link FileItem} and {@link FileField}.
   */
  public List<Pair<FileItem, FileField>> getPublicationFileFields(final PublicationDetail pub,
      final String language) {
    final Pair<PublicationTemplate, DataRecord> pubData = getOrInitializePublicationDataRecordOf(pub, language);
    try {
      return pubData.getFirst()
          .getUpdateForm()
          .getFieldTemplates()
          .stream()
          .flatMap(ft -> IntStream.range(0, ft.getMaximumNumberOfOccurrences()).mapToObj(i -> {
            final String fieldName = ft.getFieldName();
            final Field field = pubData.getSecond().getField(fieldName, i);
            if (FileField.TYPE.equals(field.getTypeName())) {
              final String inputName = Util.getFieldOccurrenceName(field.getName(), field.getOccurrence());
              final FileItem item = FileUploadUtil.getFile(items, inputName);
              if (item != null && !item.isFormField() && StringUtil.isDefined(item.getFileName())) {
                return Pair.of(item, (FileField) field);
              } else {
                return Pair.of((FileItem) null, (FileField) field);
              }
            }
            return null;
          })
          .filter(Objects::nonNull))
          .collect(Collectors.toList());
    } catch (PublicationTemplateException e) {
      throw new KmeliaRuntimeException(e);
    }
  }

  /**
   * Gets a {@link Pair} of {@link Form} and existing {@link DataRecord} of the given
   * publication if any or initializes a new {@link DataRecord} otherwise.
   * <p>
   * Searched elements are cached in order to improve the treatment processing.
   * </p>
   * @param pub the aimed publication.
   * @param language the current managed language.
   * @return a {@link Pair} of {@link Form} and {@link DataRecord}.
   * @throws KmeliaRuntimeException in case of publication template service error or in case of
   * form management error.
   */
  public Pair<PublicationTemplate, DataRecord> getOrInitializePublicationDataRecordOf(
      final PublicationDetail pub, final String language) {
    return dataRecordCache.computeIfAbsent(pub.getId() + ":" + language, i -> {
      final String externalId = pub.getInstanceId() + ":" + getXmlFormShortNameFromItems();
      final PublicationTemplate template = getPublicationTemplate(externalId);
      try {
        final RecordSet set = template.getRecordSet();
        final String pubLanguage = pub.getLanguageToDisplay(language);
        DataRecord data = set.getRecord(pub.getId(), pubLanguage);
        if (data == null || (pubLanguage != null && !pubLanguage.equals(data.getLanguage()))) {
          // This publication haven't got any content at all or for requested language
          data = set.getEmptyRecord();
          data.setId(pub.getId());
          data.setLanguage(pubLanguage);
        }
        return Pair.of(template, data);
      } catch (FormException | PublicationTemplateException e) {
        throw new KmeliaRuntimeException(e);
      }
    });
  }

  /**
   * Gets a {@link PublicationTemplate} instance from its external identifier.
   * <p>
   * The instance is cached against its external identifier.
   * </p>
   * @param externalId an external identifier as string.
   * @return a {@link PublicationTemplate} instance.
   */
  private PublicationTemplate getPublicationTemplate(final String externalId) {
    return templateCache.computeIfAbsent(externalId, i -> {
      try {
        return getPublicationTemplateManager().getPublicationTemplate(i);
      } catch (PublicationTemplateException e) {
        throw new KmaxRuntimeException(e);
      }
    });
  }

  /**
   * Gets an instance of PublicationTemplateManager.
   * @return an instance of PublicationTemplateManager.
   */
  private PublicationTemplateManager getPublicationTemplateManager() {
    return PublicationTemplateManager.getInstance();
  }
}
