/**
 * This file is part of Eclipse Steady.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * SPDX-License-Identifier: Apache-2.0
 * SPDX-FileCopyrightText: Copyright (c) 2018-2020 SAP SE or an SAP affiliate company and Eclipse Steady contributors
 */
package org.eclipse.steady.kb.util;

import java.io.File;
import java.io.IOException;
import java.util.Set;
import java.util.TreeSet;

import org.apache.logging.log4j.Logger;
import org.eclipse.steady.ConstructChange;
import org.eclipse.steady.ConstructId;
import org.eclipse.steady.FileAnalysisException;
import org.eclipse.steady.FileAnalyzer;
import org.eclipse.steady.FileAnalyzerFactory;
import org.eclipse.steady.shared.json.model.FileChange;

/**
 * <p>
 * FileComparator class.
 * </p>
 */
public class FileComparator {

  private static final Logger log = org.apache.logging.log4j.LogManager.getLogger();
  private File def, fix = null;
  private FileAnalyzer defAnalyzer, fixAnalyzer = null;

  private Set<ConstructChange> changes = null;
  private String revision = null;
  private String repo = null;
  private String repoPath = null;
  private String timeStamp = null;

  /**
   * Constructor using a FileChange (as produced by IVCSClient). Potential refactoring: Make
   * FileChange include both revision and repository path, adopt this constructor and delete the
   * other one below. As a result, fewer classes carry repository information. Right now, revision
   * and path info is kept redundantly in several classes.
   *
   * @param _c the FileChange holding the old and new file to be compared (+ the relative repo
   *        path)
   * @param _rev the revision in which the file was changed
   * @throws java.io.IOException if any.
   * @param _time_stamp a {@link java.lang.String} object.
   * @throws org.eclipse.steady.FileAnalysisException if any.
   */
  public FileComparator(FileChange _c, String _rev, String _time_stamp)
      throws FileAnalysisException, IOException {
    this(_c.getOldFile(), _c.getNewFile(), _rev, _c.getRepo(), _c.getRepoPath(), _time_stamp);
  }

  /**
   * Potential refactoring: Delete constructor (or make private), see proposal of prev.
   * constructor.
   *
   * @param _def a {@link java.io.File} object.
   * @param _fix a {@link java.io.File} object.
   * @param _rev a {@link java.lang.String} object.
   * @param _repo_path a {@link java.lang.String} object.
   * @throws java.io.IOException if any.
   * @param _repo a {@link java.lang.String} object.
   * @param _time_stamp a {@link java.lang.String} object.
   * @throws org.eclipse.steady.FileAnalysisException if any.
   */
  public FileComparator(
      File _def, File _fix, String _rev, String _repo, String _repo_path, String _time_stamp)
      throws IOException, FileAnalysisException {
    if (_def == null && _fix == null)
      throw new IllegalArgumentException(
          "The comparator requires at least one file as input, either the defective file, the"
              + " fixed file or both");
    this.revision = _rev;
    this.repo = _repo;
    this.repoPath = _repo_path;
    this.timeStamp = _time_stamp;

    if (_def != null) {
      this.def = _def;
      this.defAnalyzer = FileAnalyzerFactory.buildFileAnalyzer(_def);
      this.defAnalyzer.getConstructs();
    }
    if (_fix != null) {
      this.fix = _fix;
      this.fixAnalyzer = FileAnalyzerFactory.buildFileAnalyzer(_fix);
      this.fixAnalyzer.getConstructs();
    }
  }

  /**
   * Identifies whether programming constructs changed between the defective and fixed version.
   *
   * @return a {@link java.util.Set} object.
   */
  public Set<ConstructChange> identifyChanges() {
    if (this.changes == null) {
      this.changes = new TreeSet<ConstructChange>();

      // Loop constructs of defective file (if any)
      // Add to changes if construct has been modified (!equals) or does not exist any longer
      if (this.defAnalyzer != null) {
        try {
          for (ConstructId key : this.defAnalyzer.getConstructs().keySet()) {
            if (this.fixAnalyzer != null && this.fixAnalyzer.containsConstruct(key)) {
              if (!this.defAnalyzer.getConstruct(key).equals(this.fixAnalyzer.getConstruct(key))) {
                // Modification (MOD)
                final ConstructChange c =
                    new ConstructChange(
                        this.repo,
                        this.repoPath,
                        this.defAnalyzer.getConstruct(key),
                        this.fixAnalyzer.getConstruct(key),
                        this.revision,
                        this.timeStamp);

                // Always add the construct change, no matter the signature change
                // (if any)
                // if(c.hasSignatureChange())
                this.changes.add(c);
              }
            } else {
              this.changes.add(
                  new ConstructChange(
                      this.repo,
                      this.repoPath,
                      this.defAnalyzer.getConstruct(key),
                      null,
                      this.revision,
                      this.timeStamp));
            }
          }
        } catch (FileAnalysisException e) {
          FileComparator.log.error(
              "Error while comparing defective and fixed version: {}", e.getMessage(), e);
        }
      }
      // Loop constructs of fixed file (if any)
      // Add to changes if construct has been modified (!equals) or does not exist any longer
      if (this.fixAnalyzer != null) {
        try {
          for (ConstructId key : this.fixAnalyzer.getConstructs().keySet()) {
            if (this.defAnalyzer != null && this.defAnalyzer.containsConstruct(key)) {
              if (!this.fixAnalyzer.getConstruct(key).equals(this.defAnalyzer.getConstruct(key))) {
                // Modification (MOD)
                final ConstructChange chg =
                    new ConstructChange(
                        this.repo,
                        this.repoPath,
                        this.defAnalyzer.getConstruct(key),
                        this.fixAnalyzer.getConstruct(key),
                        this.revision,
                        this.timeStamp);

                // Always add the construct change if not existing yet, no matter
                // the signature
                // change (if any)
                if (!this.changes.contains(chg)) // && chg.hasSignatureChange())
                this.changes.add(chg);
              }
            } else {
              this.changes.add(
                  new ConstructChange(
                      this.repo,
                      this.repoPath,
                      null,
                      this.fixAnalyzer.getConstruct(key),
                      this.revision,
                      this.timeStamp));
            }
          }
        } catch (FileAnalysisException e) {
          FileComparator.log.error(
              "Error while comparing defective and fixed version: {}", e.getMessage(), e);
        }
      }
    }
    return changes;
  }
}
