/*
 * RHQ Management Platform
 * Copyright (C) 2011 Red Hat, Inc.
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation version 2 of the License.
 *
 * 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

package org.rhq.core.domain.drift;

import java.util.Comparator;
import java.util.List;

/**
 * Compares two {@link DriftDefinition} objects. The comparison can either
 * compare basedir/includes-excludes filters or ignore them. Will also compare
 * name, interval, enabled flag.
 * 
 * @author John Mazzitelli
 */
public class DriftDefinitionComparator implements Comparator<DriftDefinition> {

    public enum CompareMode {
        /**
         * The comparator will only check the base information: name, description, enabled,
         * interval, driftHandlingMode, pinned, and pinnedVersion. The basedir/includes-excludes
         * filters will be ignored.
         */
        ONLY_BASE_INFO,

        /**
         * The comparator will only check the basedir/includes-excludes filters. 
         * The base information will be ignored (see {@link #ONLY_BASE_INFO} for what those are).
         */
        ONLY_DIRECTORY_SPECIFICATIONS,

        /**
         * The comparator will check both the basedir/includes-excludes filters and 
         * the base information (see {@link #ONLY_BASE_INFO} for what those are).
         */
        BOTH_BASE_INFO_AND_DIRECTORY_SPECIFICATIONS;
    };

    private final CompareMode compareMode;

    public DriftDefinitionComparator(CompareMode mode) {
        this.compareMode = mode;
    }

    @Override
    public int compare(DriftDefinition dc1, DriftDefinition dc2) {
        if (compareMode != CompareMode.ONLY_DIRECTORY_SPECIFICATIONS) {
            if (dc1.getName() != null) {
                if (dc2.getName() != null) {
                    int results = dc1.getName().compareTo(dc2.getName());
                    if (results != 0) {
                        return results;
                    }
                } else {
                    return 1; // dc1's name is not null, but dc2's name is null, not equal!
                }
            } else if (dc2.getName() != null) {
                return -1; // dc1's name is null, but dc2's name is not null, not equal!
            }

            if (dc1.getDescription() != null) {
                if (dc2.getDescription() != null) {
                    int results = dc1.getDescription().compareTo(dc2.getDescription());
                    if (results != 0) {
                        return results;
                    }
                } else {
                    return 1;  // dc1's description is not null, but dc2's description is null
                }
            } else if (dc2.getDescription() != null) {
                return -1;  // dc2's description is not null, but dc1's description is null
            }

            if (dc1.getInterval() != dc2.getInterval()) {
                return dc1.getInterval() < dc2.getInterval() ? -1 : 1;
            }

            if (dc1.getDriftHandlingMode() != dc2.getDriftHandlingMode()) {
                return dc1.getDriftHandlingMode().ordinal() < dc2.getDriftHandlingMode().ordinal() ? -1 : 1;
            }

            if (dc1.isEnabled() != dc2.isEnabled()) {
                return dc1.isEnabled() ? 1 : -1; // so false sorts before true, seems logical to me
            }

            if (dc1.isAttached() != dc2.isAttached()) {
                return dc1.isAttached() ? 1 : -1;
            }

            if (dc1.isPinned() != dc2.isPinned()) {
                return dc1.isPinned() ? 1 : -1;
            }
        }

        if (compareMode != CompareMode.ONLY_BASE_INFO) {
            if (dc1.getBasedir() != null) {
                if (!dc1.getBasedir().equals(dc2.getBasedir())) {
                    int hash1 = dc1.getBasedir().hashCode();
                    int hash2 = (dc2.getBasedir() != null) ? dc2.getBasedir().hashCode() : 0;
                    return hash1 < hash2 ? -1 : 1;
                }
            } else if (dc2.getBasedir() != null) {
                return -1; // dc1's basedir is null, but dc2's basedir is not null, not equal!
            }

            List<Filter> filters1 = dc1.getIncludes();
            List<Filter> filters2 = dc2.getIncludes();
            int results = compareFilters(filters1, filters2);
            if (results != 0) {
                return results;
            }

            filters1 = dc1.getExcludes();
            filters2 = dc2.getExcludes();
            results = compareFilters(filters1, filters2);
            if (results != 0) {
                return results;
            }
        }

        return 0;
    }

    private int compareFilters(List<Filter> filters1, List<Filter> filters2) {
        if (filters1 == null) {
            return (filters2 == null) ? 0 : -1;
        } else if (filters2 == null) {
            return 1;
        }

        // at this point, we know both filters1 and filters2 are non-null
        // let's do the next easiest check - size. If the lists are different sizes, then clearly they are different
        if (filters1.size() != filters2.size()) {
            return (filters1.size() < filters2.size()) ? -1 : 1;
        }

        // they are the same size, let's see if they have the same items. We only need to see if one contains
        // all of the other; because they are the same size, if filters1 contains all the filters2 items,
        // then clearly the other way is true too (filters2 contains all of filters1 items).
        if (!filters1.containsAll(filters2)) {
            return -1; // unsure if we need to do further anaylsis to determine if -1 or 1 should be returned; for now, don't worry about it
        }

        return 0;
    }
}
