/*
 * Copyright (c) 2021 OBiBa. All rights reserved.
 *
 * This program and the accompanying materials
 * are made available under the terms of the GNU Public License v3.0.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.obiba.opal.core.vcs.command;

import com.google.common.base.Charsets;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.thoughtworks.xstream.XStream;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.transport.PushResult;
import org.obiba.git.GitException;
import org.obiba.git.command.AbstractGitWriteCommand;
import org.obiba.magma.MagmaEngine;
import org.obiba.magma.ValueTable;
import org.obiba.magma.ValueView;
import org.obiba.magma.Variable;
import org.obiba.magma.js.views.JavascriptClause;
import org.obiba.magma.views.View;
import org.obiba.magma.views.WhereClause;
import org.obiba.magma.views.support.VariableOperationContext;
import org.obiba.magma.xstream.MagmaXStreamExtension;
import org.obiba.opal.core.vcs.OpalGitUtils;

import javax.validation.constraints.NotNull;
import java.io.*;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static org.obiba.magma.views.support.VariableOperationContext.Operation;

public class OpalWriteViewsCommand extends AbstractGitWriteCommand {

  private final Set<ValueView> views;

  private final VariableOperationContext context;

  private OpalWriteViewsCommand(@NotNull File repositoryPath, @NotNull Set<ValueView> views, @NotNull String commitMessage,
                                VariableOperationContext context) {
    super(repositoryPath, OpalGitUtils.getGitViewsWorkFolder(), commitMessage);
    this.views = views;
    this.context = context;
  }

  @Override
  public Iterable<PushResult> execute(Git git) {
    try {
      File localRepo = git.getRepository().getWorkTree();
      List<String> varFilesToRemove = Lists.newArrayList();
      StringBuilder message = new StringBuilder();
      serializeAllViewFiles(localRepo, varFilesToRemove, message);
      String currentMessage = getCommitMessage();
      setCommitMessage((Strings.isNullOrEmpty(currentMessage) ? "Update " : currentMessage + " ") + message);

      for (String toRemove : varFilesToRemove) {
        git.rm().addFilepattern(toRemove).call();
      }
      git.add().addFilepattern(".").call();
      return commitAndPush(git);
    } catch (IOException | GitAPIException e) {
      throw new GitException(e);
    }
  }

  private void serializeAllViewFiles(File localRepo, Collection<String> varFilesToRemove, StringBuilder message)
      throws IOException {
    message.append("[");
    for (ValueView view : views) {
      doWriteGitView(localRepo, view, varFilesToRemove);
      if (message.length() > 1) {
        message.append(", ");
      }
      message.append(view.getName());
    }
    message.append("]");
  }

  private void doWriteGitView(File localRepo, ValueView view, Collection<String> varFilesToRemove) throws IOException {
    File viewRepo = new File(localRepo, view.getName());
    viewRepo.mkdirs();

    // Write serialized view
    File viewFile = new File(viewRepo, OpalGitUtils.VIEW_FILE_NAME);
    try (OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(viewFile), Charsets.UTF_8)) {
      getXStream().toXML(view, writer);
    }

    if (view instanceof View) {
      doWriteGitViewWhere(viewRepo, (View) view, varFilesToRemove);
    }
    if (context != null && context.hasOperations(view)) {
      doWriteGitViewVariables(viewRepo, view, context.getOperations(view), varFilesToRemove);
    } else {
      doWriteGitViewVariables(viewRepo, view, varFilesToRemove);
    }
  }

  private void doWriteGitViewWhere(File viewRepo, View view, Collection<String> varFilesToRemove) throws IOException {
    File scriptFile = new File(viewRepo, OpalGitUtils.VIEW_WHERE_FILE_NAME);
    WhereClause where = view.getWhereClause();

    if (where != null && where instanceof JavascriptClause) {
      String script = ((JavascriptClause) where).getScript();
      try (FileWriter fileWriter = new FileWriter(scriptFile)) {
        fileWriter.append(script);
        fileWriter.flush();
      }
    } else if (scriptFile.exists()) {
      varFilesToRemove.add(viewRepo.getName() + "/" + scriptFile.getName());
    }
  }

  private void doWriteGitViewVariables(File viewRepo, ValueTable view, Map<Operation, Collection<Variable>> operations,
                                       Collection<String> varFilesToRemove)
      throws IOException {
    // Write variable script files
    Collection<Variable> variablesToWrite = operations.get(Operation.ADD);
    if (variablesToWrite != null) {
      for (Variable variable : variablesToWrite) {
        doWriteGitViewVariable(viewRepo, variable);
      }
    }

    // Remove variable script files
    Collection<Variable> variablesToDelete = operations.get(Operation.DELETE);
    if (variablesToDelete != null) {
      for (Variable variable : variablesToDelete) {
        varFilesToRemove.add(OpalGitUtils.getVariableFilePath(view.getName(), variable.getName()));
      }
    }

  }

  private void doWriteGitViewVariables(File viewRepo, ValueTable view, Collection<String> varFilesToRemove)
      throws IOException {
    // Write variable script files
    for (Variable variable : view.getVariables()) {
      doWriteGitViewVariable(viewRepo, variable);
    }
    // Detect removed variables

    for (File f : viewRepo.listFiles(new FileFilter() {
      @Override
      public boolean accept(File pathname) {
        return pathname.getName().endsWith(OpalGitUtils.VARIABLE_FILE_EXTENSION);
      }
    })) {
      String varName = f.getName().substring(0, f.getName().length() - 3);
      if (!view.hasVariable(varName)) {
        varFilesToRemove.add(f.getParentFile().getName() + File.separator + f.getName());
      }
    }
  }

  private void doWriteGitViewVariable(File viewRepo, Variable variable) throws IOException {
    // is column mapping?
    if (!variable.hasAttribute("opal", "column")) {
      String script = variable.hasAttribute("script") ? variable.getAttributeStringValue("script") : "null";
      File variableFile = new File(viewRepo, variable.getName() + OpalGitUtils.VARIABLE_FILE_EXTENSION);

      try (FileWriter fileWriter = new FileWriter(variableFile)) {
        fileWriter.append(script);
        fileWriter.flush();
      }
    }
  }

  private XStream getXStream() {
    return MagmaEngine.get().getExtension(MagmaXStreamExtension.class).getXStreamFactory().createXStream();
  }

  public static class Builder {

    private final OpalWriteViewsCommand command;

    public Builder(@NotNull File repositoryPath, @NotNull Set<ValueView> views, @NotNull String comment, VariableOperationContext context) {
      command = new OpalWriteViewsCommand(repositoryPath, views, comment, context);
    }

    public OpalWriteViewsCommand build() {
      return command;
    }
  }

}
