/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */
package org.apache.jackrabbit.vault.util.diff;

import java.util.Arrays;
import java.util.List;

import difflib.Chunk;
import difflib.Delta;
import difflib.DiffUtils;
import difflib.Patch;

/**
 * A class to compare vectors of objects.
 */
public class Diff {

    /**
     * Left list of elements
     */
    private final List<?> left;

    /**
     * Right list of elements
     */
    private final List<?> right;

    /**
     * Constructor to find differences between two arrays.
     * @param a left "document"
     * @param b right document
     */
    public Diff(Object[] a, Object[] b) {
        left = Arrays.asList(a);
        right = Arrays.asList(b);
    }

    /**
     * When set to true, the comparison uses a heuristic to speed it up.
     * With this heuristic, for files with a constant small density
     * of changes, the algorithm is linear in the file size.
     */
    @Deprecated
    public boolean heuristic = false;

    /**
     * When set to true, the algorithm returns a guaranteed minimal
     * set of changes.  This makes things slower, sometimes much slower.
     */
    @Deprecated
    public boolean no_discards = false;

    /**
     * Compute the difference between the 2 arrays.
     * @param reverse Indicates reverse diff
     * @return the change
     */
    public Change diff_2(final boolean reverse) {
        Change prev = new Change(0, 0, 0, 0, null);
        Change ret = prev;
        Patch p = DiffUtils.diff(left, right);

        // recompute the changes based on the deltas.
        // todo: use the deltas directly in the DocumentDiff.
        for (Delta d : p.getDeltas()) {
            Chunk c0 = d.getOriginal();
            Chunk c1 = d.getRevised();
            Change next = new Change(
                    c0.getPosition(),
                    c1.getPosition(),
                    c0.getLines().size(),
                    c1.getLines().size(),
                    null);

            if (reverse) {
                next.nextChange = ret;
                ret = next;
            } else {
                prev.nextChange = next;
                prev = next;
            }
        }
        return reverse ? ret : ret.nextChange;
    }

    /**
     * The result of the diff.
     */
    public static class Change {

        /**
         * Previous or next edit command.
         */
        public Change nextChange;

        /**
         * # lines of file 1 changed here.
         */
        public final int inserted;

        /**
         * # lines of file 0 changed here.
         */
        public final int deleted;

        /**
         * Line number of 1st deleted line.
         */
        public final int line0;

        /**
         * Line number of 1st inserted line.
         */
        public final int line1;

        /**
         * Creates a new change entry. If {@code deleted} is 0, then {@code line0} is the number of the line before the
         * insertion was done. If {@code inserted} is 0, then {@code line1} is the number of the line before the deletion
         * was done.
         *
         * @param line0 first affected line in the left file.
         * @param line1 first affected line in the right file.
         * @param deleted number of deleted lines from left file.
         * @param inserted number of inserted lines in right file
         * @param old previous change.
         */
        Change(int line0, int line1, int deleted, int inserted, Change old) {
            this.line0 = line0;
            this.line1 = line1;
            this.inserted = inserted;
            this.deleted = deleted;
            this.nextChange = old;
        }
    }
}
