/**
 * 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.cia.dependencyfinder;

import java.util.Iterator;

import com.jeantessier.classreader.Class_info;
import com.jeantessier.classreader.Classfile;
import com.jeantessier.diff.ClassDifferences;
import com.jeantessier.diff.Differences;
import com.jeantessier.diff.InterfaceDifferences;
import com.jeantessier.diff.PackageDifferences;
import com.jeantessier.diff.ProjectDifferences;
import com.jeantessier.diff.VisitorBase;

import org.eclipse.steady.shared.enums.ConstructType;
import org.eclipse.steady.shared.enums.ProgrammingLanguage;
import org.eclipse.steady.shared.json.model.Artifact;
import org.eclipse.steady.shared.json.model.ConstructId;
import org.eclipse.steady.shared.json.model.diff.JarDiffResult;

/**
 * Visits all kinds of changes and creates instances of the classes contained in
 * package {@link org.eclipse.steady.shared.json.model}. Inspired from
 * {@link com.jeantessier.diff.Report}.
 */
public class JarDiffVisitor extends VisitorBase {

  private JarDiffResult jarDiffResult = new JarDiffResult();

  /**
   * <p>Constructor for JarDiffVisitor.</p>
   *
   * @param _old a {@link org.eclipse.steady.shared.json.model.Artifact} object.
   * @param _new a {@link org.eclipse.steady.shared.json.model.Artifact} object.
   */
  public JarDiffVisitor(Artifact _old, Artifact _new) {
    this.jarDiffResult.setOldLibId(_old.getLibId());
    this.jarDiffResult.setNewLibId(_new.getLibId());
  }

  /**
   * <p>Getter for the field <code>jarDiffResult</code>.</p>
   *
   * @return a {@link org.eclipse.steady.shared.json.model.diff.JarDiffResult} object.
   */
  public JarDiffResult getJarDiffResult() {
    return jarDiffResult;
  }

  /**
   * <p>Setter for the field <code>jarDiffResult</code>.</p>
   *
   * @param jarDiffResult a {@link org.eclipse.steady.shared.json.model.diff.JarDiffResult} object.
   */
  public void setJarDiffResult(JarDiffResult jarDiffResult) {
    this.jarDiffResult = jarDiffResult;
  }

  /** {@inheritDoc} */
  public void visitProjectDifferences(ProjectDifferences differences) {
    for (Differences packageDifference : differences.getPackageDifferences()) {
      packageDifference.accept(this);
    }
  }

  /** {@inheritDoc} */
  public void visitPackageDifferences(PackageDifferences differences) {
    if (differences.isRemoved()) {
      jarDiffResult.addDeletedPackage(
          new org.eclipse.steady.shared.json.model.ConstructId(
              ProgrammingLanguage.JAVA, ConstructType.PACK, differences.getName()));
    }
    for (Differences classDiffenrence : differences.getClassDifferences()) {
      classDiffenrence.accept(this);
    }
    if (differences.isNew()) {
      jarDiffResult.addNewPackage(
          new org.eclipse.steady.shared.json.model.ConstructId(
              ProgrammingLanguage.JAVA, ConstructType.PACK, differences.getName()));
    }
  }

  private org.eclipse.steady.shared.json.model.ConstructId getClass(Classfile _classfile) {
    org.eclipse.steady.shared.json.model.ConstructId c =
        new org.eclipse.steady.shared.json.model.ConstructId();

    c.setLang(ProgrammingLanguage.JAVA);
    c.setQname(_classfile.getClassName());

    if (_classfile.isDeprecated())
      c.addRelates(
          "annotation",
          new ConstructId(ProgrammingLanguage.JAVA, ConstructType.INTF, "java.lang.Deprecated"));

    c.addAttribute("final", _classfile.isFinal());
    c.addAttribute("synthetic", _classfile.isSynthetic());
    c.addAttribute("super", _classfile.isSuper());

    // Visibility
    if (_classfile.isPublic()) c.addAttribute("visibility", "public");
    else if (_classfile.isPackage()) c.addAttribute("visibility", "package");

    // Extends and Implements
    if (_classfile.isInterface()) {
      c.setType(ConstructType.INTF);
      Iterator<? extends Class_info> i = _classfile.getAllInterfaces().iterator();
      while (i.hasNext()) {
        c.addRelates(
            "extends",
            new ConstructId(ProgrammingLanguage.JAVA, ConstructType.INTF, i.next().toString()));
      }
    } else {
      c.setType(ConstructType.CLAS);
      c.addRelates(
          "extends",
          new ConstructId(
              ProgrammingLanguage.JAVA, ConstructType.CLAS, _classfile.getSuperclassName()));
      c.addAttribute("abstract", _classfile.isAbstract());
      Iterator<? extends Class_info> i = _classfile.getAllInterfaces().iterator();
      while (i.hasNext()) {
        c.addRelates(
            "implements",
            new ConstructId(ProgrammingLanguage.JAVA, ConstructType.INTF, i.next().toString()));
      }
    }

    return c;
  }

  /** {@inheritDoc} */
  public void visitClassDifferences(ClassDifferences differences) {
    if (differences.isRemoved()) {
      jarDiffResult.addDeletedClass(this.getClass(differences.getOldClass()));
    }

    if (differences.isModified()) {
      ClassDiffVisitor visitor = new ClassDiffVisitor(this.getClass(differences.getNewClass()));
      differences.accept(visitor);
      jarDiffResult.addModifiedClass(visitor.getClassDiffResult());
    }

    if (differences.isNew()) {
      jarDiffResult.addNewClass(this.getClass(differences.getNewClass()));
    }

    if (isDeprecated()) {
      jarDiffResult.addDeprecatedClass(this.getClass(differences.getNewClass()));
    }

    if (isUndeprecated()) {
      jarDiffResult.addUndeprecatedClass(this.getClass(differences.getNewClass()));
    }
  }

  /** {@inheritDoc} */
  public void visitInterfaceDifferences(InterfaceDifferences differences) {
    if (differences.isRemoved()) {
      jarDiffResult.addDeletedInterface(this.getClass(differences.getOldClass()));
    }

    if (differences.isModified()) {
      ClassDiffVisitor visitor = new ClassDiffVisitor(this.getClass(differences.getNewClass()));
      differences.accept(visitor);
      jarDiffResult.addModifiedInterface(visitor.getClassDiffResult());
    }

    if (differences.isNew()) {
      jarDiffResult.addNewInterface(this.getClass(differences.getNewClass()));
    }

    if (isDeprecated()) {
      jarDiffResult.addDeprecatedInterface(this.getClass(differences.getNewClass()));
    }

    if (isUndeprecated()) {
      jarDiffResult.addUndeprecatedInterface(this.getClass(differences.getNewClass()));
    }
  }
}
