/*
 * Copyright (c) 1997 - 2016
 * Actelion Pharmaceuticals Ltd.
 * Gewerbestrasse 16
 * CH-4123 Allschwil, Switzerland
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 3. Neither the name of the the copyright holder nor the
 *    names of its contributors may be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * @author Modest v. Korff
 */

package com.actelion.research.chem.descriptor.flexophore.redgraph;

import com.actelion.research.util.hash.HashSetInt;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

/**
 * SubGraphIndices
 *
 * A class to handle indices in a molecular graph.
 *
 * It is just a hash set for integer with some methods.
 *
 * Created by korffmo1 on 23.02.16.
 */
public class SubGraphIndices {

    private HashSetInt hsIndexAtom;




    public SubGraphIndices() {
        hsIndexAtom = new HashSetInt();
    }

    public SubGraphIndices(int[] arrIndexAtom) {
        hsIndexAtom = new HashSetInt(arrIndexAtom);
    }

    public int getNumIndices() {
        return hsIndexAtom.size();
    }

    public void clear() {
        hsIndexAtom.clear();
    }

    public void addIndex(int indexAtom) {
        hsIndexAtom.add(indexAtom);
    }

    public void addIndex(int[] arrIndexAtom) {
        hsIndexAtom.add(arrIndexAtom);
    }

    public int[] getAtomIndices() {
        return hsIndexAtom.getValues();
    }

    public boolean contains(int indexAtom) {
        return hsIndexAtom.contains(indexAtom);
    }
    public boolean equalIndices(int [] arrIndexAtom) {
        if(hsIndexAtom.size()!=arrIndexAtom.length)
            return false;

        boolean eq=true;

        for (int index : arrIndexAtom) {
            if(!hsIndexAtom.contains(index)){
                eq=false;
                break;
            }
        }

        return eq;
    }

    @Override
    public boolean equals(Object obj) {
        if(!(obj instanceof SubGraphIndices)){
            return false;
        }

        SubGraphIndices sg = (SubGraphIndices) obj;
        if(hsIndexAtom.size() != sg.getNumIndices()){
            return false;
        }

        int [] a0 = hsIndexAtom.getValues();
        int [] a1 = sg.getAtomIndices();
        Arrays.sort(a0);
        Arrays.sort(a1);
        boolean eq = true;
        for (int i = 0; i < a0.length; i++) {
            if(a0[i] != a1[i]){
                eq = false;

            }
        }

        return eq;
    }

    public boolean isOverlap(SubGraphIndices frag) {

        boolean overlap = false;
        int[] arrFragIndexAt = frag.hsIndexAtom.getValues();
        for (int indexAtomFrag : arrFragIndexAt) {
            if (hsIndexAtom.contains(indexAtomFrag)) {
                overlap = true;
                break;
            }
        }
        return overlap;

    }

    public int getNumOverlappingIndices(SubGraphIndices frag) {

        int nOverlapping = 0;

        int[] arrFragIndexAt = frag.hsIndexAtom.getValues();
        for (int indexAtomFrag : arrFragIndexAt) {
            if (hsIndexAtom.contains(indexAtomFrag)) {
                nOverlapping++;
            }
        }
        return nOverlapping;
    }

    public void merge(SubGraphIndices frag) {
        int[] arrFragIndexAt = frag.hsIndexAtom.getValues();
        for (int indexAtomFrag : arrFragIndexAt) {
            hsIndexAtom.add(indexAtomFrag);
        }
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder();
        int [] a = hsIndexAtom.getValues();
        Arrays.sort(a);
        for (int i = 0; i < a.length; i++) {
            sb.append(a[i]);
            if(i<a.length-1){
                sb.append(",");
            }
        }

        return sb.toString();
    }

    /**
     * Merges fragments containing a common atom index. The fragments in the list are merged and the list is
     * shrinked.
     *
     * @param liFragment
     */
    public static void merge(List<SubGraphIndices> liFragment) {
        boolean merged = true;
        while (merged) {
            merged = false;
            for (int i = 0; i < liFragment.size(); i++) {
                SubGraphIndices frag1 = liFragment.get(i);
                for (int j = liFragment.size() - 1; j > i; j--) {
                    SubGraphIndices frag2 = liFragment.get(j);
                    if (frag1.isOverlap(frag2)) {
                        frag1.merge(frag2);
                        liFragment.remove(j);
                        merged = true;
                    }
                }
            }
        }
    }

    /**
     * Merges overlapping fragments. They may share a minimum number of overlapping indices.
     * @param liFragment
     * @param minNumIndicesOverlapping
     * @return
     */
    public static List<SubGraphIndices> mergeOverlapping(List<SubGraphIndices> liFragment, int minNumIndicesOverlapping) {

        List<OverlappingFragments> liOverlappingFragments = new ArrayList<>();
        for (int i = 0; i < liFragment.size(); i++) {
            SubGraphIndices frag1 = liFragment.get(i);
            OverlappingFragments overlappingFragments = new OverlappingFragments(frag1);
            liOverlappingFragments.add(overlappingFragments);

        }

        boolean merged = true;
        while (merged) {
            merged = false;
            for (int i = 0; i < liOverlappingFragments.size(); i++) {
                OverlappingFragments of1 = liOverlappingFragments.get(i);
                for (int j = liOverlappingFragments.size() - 1; j > i; j--) {
                    OverlappingFragments of2 = liOverlappingFragments.get(j);
                    if (of1.getNumLargestOverlap(of2) >= minNumIndicesOverlapping) {
                        of1.add(of2);
                        liOverlappingFragments.remove(j);
                    }
                }
            }
        }

        List<SubGraphIndices> liFragmentMerged = new ArrayList<>();
        for (OverlappingFragments olf : liOverlappingFragments) {
            liFragmentMerged.add(olf.getSubGraphIndices());
        }

        return liFragmentMerged;
    }

    /**
     * Adds the atom indices to the hash set.
     * @param hs
     * @param liFragment
     */
    protected static void addAtomIndices(HashSetInt hs, List<SubGraphIndices> liFragment) {
        for (SubGraphIndices fragment : liFragment) {
            hs.add(fragment.getAtomIndices());
        }
    }

    public static Comparator<SubGraphIndices> getComparatorNumIndices() {

        return new Comparator<SubGraphIndices>() {
            @Override
            public int compare(SubGraphIndices f1, SubGraphIndices f2) {
                int cmp = 0;
                if (f1.hsIndexAtom.size() > f2.hsIndexAtom.size()) {
                    cmp = 1;
                } else if (f1.hsIndexAtom.size() < f2.hsIndexAtom.size()) {
                    cmp = -1;
                }
                return cmp;
            }
        };
    }

    private static class OverlappingFragments {
        List<SubGraphIndices> liSubGraphIndices;
        public OverlappingFragments(SubGraphIndices sg) {
            liSubGraphIndices = new ArrayList<>();
            liSubGraphIndices.add(sg);
        }

       public OverlappingFragments(SubGraphIndices sg1, SubGraphIndices sg2) {
            liSubGraphIndices = new ArrayList<>();
            liSubGraphIndices.add(sg1);
            liSubGraphIndices.add(sg2);
        }


        public void add(SubGraphIndices sg){
            liSubGraphIndices.add(sg);
        }

        public void add(OverlappingFragments of){
            liSubGraphIndices.addAll(of.liSubGraphIndices);
        }


        public int getNumLargestOverlap(OverlappingFragments of){
            int nOverlapMax = 0;
            for (SubGraphIndices sgi0 : liSubGraphIndices) {
                for (SubGraphIndices sgi1 : of.liSubGraphIndices) {
                    int n = sgi0.getNumOverlappingIndices(sgi1);
                    if(n > nOverlapMax){
                        nOverlapMax = n;
                    }
                }
            }
            return nOverlapMax;
        }

        public boolean containsSubGraph(OverlappingFragments of) {
            boolean contains = false;
            containsB:
            for (SubGraphIndices sgi1 : liSubGraphIndices) {
                for (SubGraphIndices sgi2 : of.liSubGraphIndices) {
                    if(sgi1.equals(sgi2)){
                        contains = true;
                        break containsB;
                    }
                }
            }
            return contains;
        }

        public SubGraphIndices getSubGraphIndices(){
            SubGraphIndices.merge(liSubGraphIndices);
            if(liSubGraphIndices.size() != 1) {
                throw new RuntimeException("This should not happen.");
            }
            return liSubGraphIndices.get(0);
        }
    }
}
