/*
 * RHQ Management Platform
 * Copyright (C) 2005-2010 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, version 2, as
 * published by the Free Software Foundation, and/or the GNU Lesser
 * General Public License, version 2.1, also as published by the Free
 * Software Foundation.
 *
 * 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 and the GNU Lesser General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License
 * and the GNU Lesser General Public License along with this program;
 * if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

package org.rhq.enterprise.server.resource.disambiguation;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * This class partitions the reports inserted into it by chunking them up 
 * into sublists containing mutually ambiguous reports. The ambiguity is determined using 
 * a {@link DisambiguationPolicy}.
 * 
 * @param <T> the type of the original instances the are being disambiguated.
 * 
 * @author Lukas Krejci
 */
public class ReportPartitions<T> {

    private static final long serialVersionUID = 1L;
    private DisambiguationPolicy disambiguationPolicy;
    private List<List<MutableDisambiguationReport<T>>> uniquePartitions;
    private List<List<MutableDisambiguationReport<T>>> ambiguousPartitions;
    
    /**
     * Constructs a new instance with no partitions in it.
     * 
     * @param disambiguationPolicy the policy that decides if reports are ambiguous.
     */
    public ReportPartitions(DisambiguationPolicy disambiguationPolicy) {
        this.disambiguationPolicy = disambiguationPolicy;
        uniquePartitions = new ArrayList<List<MutableDisambiguationReport<T>>>();
        ambiguousPartitions = new ArrayList<List<MutableDisambiguationReport<T>>>();
    }

    public ReportPartitions(DisambiguationPolicy disambiguationPolicy, ReportPartitions<T> other) {
        this(disambiguationPolicy);
        putAll(other);
    }
    
    public ReportPartitions(DisambiguationPolicy disambiguationPolicy, List<List<MutableDisambiguationReport<T>>> partitions) {
        this(disambiguationPolicy);
        for(List<MutableDisambiguationReport<T>> partition : partitions) {
            putAll(partition);
        }
    }
    
    public DisambiguationPolicy getDisambiguationPolicy() {
        return disambiguationPolicy;
    }

    public List<List<MutableDisambiguationReport<T>>> getAmbiguousPartitions() {
        return Collections.unmodifiableList(ambiguousPartitions);
    }

    public List<List<MutableDisambiguationReport<T>>> getUniquePartitions() {
        return Collections.unmodifiableList(uniquePartitions);
    }

    public List<List<MutableDisambiguationReport<T>>> getAllPartitions() {
        List<List<MutableDisambiguationReport<T>>> ret = new ArrayList<List<MutableDisambiguationReport<T>>>(ambiguousPartitions);
        ret.addAll(uniquePartitions);
        return Collections.unmodifiableList(ret);
    }
    
    public void put(MutableDisambiguationReport<T> value) {
        put(value, true);
    }

    public void putAll(List<MutableDisambiguationReport<T>> values) {
        for (MutableDisambiguationReport<T> v : values) {
            put(v, false);
        }
        updatePolicy();
    }

    public void putAll(ReportPartitions<T> other) {
        for (List<MutableDisambiguationReport<T>> partition : other.getAllPartitions()) {
            for (MutableDisambiguationReport<T> v : partition) {
                put(v, false);
            }
        }
        updatePolicy();
    }
    
    public boolean isPartitionsUnique() {
        return ambiguousPartitions.size() == 0;
    }

    public String toString() {
        return "ReportPartitions[policy=" + disambiguationPolicy + ", uniquePartitions=" + uniquePartitions + ", ambiguousPartitions=" + ambiguousPartitions + "]";
    }
    
    private void put(MutableDisambiguationReport<T> value, boolean updatePolicy) {
        if (insertIntoExisting(value, ambiguousPartitions) >= 0) {
            return;
        } else {    
            int idx = insertIntoExisting(value, uniquePartitions);
            if (idx >= 0) {
                ambiguousPartitions.add(uniquePartitions.remove(idx));
            } else {
                List<MutableDisambiguationReport<T>> newPartition = new ArrayList<MutableDisambiguationReport<T>>();
                newPartition.add(value);
                uniquePartitions.add(newPartition);
            }
        }
        
        if (updatePolicy) {
            updatePolicy();
        }
    }
    
    private void updatePolicy() {
        this.disambiguationPolicy.getCurrentLevel().setDeciding(uniquePartitions.size() > 0);
    }
    
    private int insertIntoExisting(MutableDisambiguationReport<T> value, List<List<MutableDisambiguationReport<T>>> partitions) {
        int idx = -1;
        boolean found = false;
        for (List<MutableDisambiguationReport<T>> partition : partitions) {
            for (MutableDisambiguationReport<T> partitionPrototype : partition) {
                if (disambiguationPolicy.areAmbiguous(partitionPrototype, value)) {
                    found = true;
                    break;
                }
            }

            idx++;
            
            if (found) {
                partition.add(value);
                break;
            }
        }
        
        return found ? idx : -1;
    }
}