/*
 * 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.
 */
package org.apache.aries.subsystem.core.archive;

import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.aries.subsystem.core.capabilityset.SimpleFilter;
import org.osgi.framework.VersionRange;

public abstract class AbstractClauseBasedHeader<C extends Clause> implements Header<C> {
	protected static VersionRange parseVersionRange(List<SimpleFilter> filters) {
		SimpleFilter floor = null;
		SimpleFilter ceiling = null;
		for (SimpleFilter filter : filters) {
			switch (filter.getOperation()) {
				case SimpleFilter.EQ:
				case SimpleFilter.GTE:
					floor = filter;
					break;
				case SimpleFilter.LTE:
					ceiling = filter;
					break;
				case SimpleFilter.NOT:
					SimpleFilter negated = ((List<SimpleFilter>)filter.getValue()).get(0);
					switch (negated.getOperation()) {
						case SimpleFilter.EQ:
						case SimpleFilter.GTE:
							ceiling = filter;
							break;
						case SimpleFilter.LTE:
							floor = filter;
							break;
						default:
							throw new IllegalArgumentException("Invalid filter: " + filter);
					}
					break;
				case SimpleFilter.PRESENT:
					/* This can happen with version ranges of the form 
					 * (1.5.0,2.0.0). The filter form will be
					 * (&(version=*)(!(version<=1.5.0))(!(version>=2.0.0)). The
					 * presence operator is required because an absent version
					 * attribute would otherwise match. These should simply be
					 * ignored for the purposes of converting back into a
					 * version range.
					 */
					break;
				default:
					throw new IllegalArgumentException("Invalid filter: " + filter);
			}
		}
		if (ceiling == null) {
			return new VersionRange(String.valueOf(floor.getValue()));
		}
		String range = new StringBuilder()
			.append(floor.getOperation() == SimpleFilter.NOT ? '(' : '[')
			.append(floor.getOperation() == SimpleFilter.NOT ? 
					((List<SimpleFilter>)floor.getValue()).get(0).getValue() : floor.getValue())
			.append(',')
			.append(ceiling.getOperation() == SimpleFilter.NOT ? 
					((List<SimpleFilter>)ceiling.getValue()).get(0).getValue() : ceiling.getValue())
			.append(ceiling.getOperation() == SimpleFilter.NOT ? ')' : ']')
			.toString();
		return new VersionRange(range);
	}
	
	private static <C> Collection<C> computeClauses(String header, ClauseFactory<C> factory) {
		Collection<String> clauseStrs = new ClauseTokenizer(header).getClauses();
		Set<C> clauses = new HashSet<C>(clauseStrs.size());
		for (String clause : clauseStrs) {
			clauses.add(factory.newInstance(clause));
		}
		return clauses;
	}
	
	public interface ClauseFactory<C> {
		public C newInstance(String clause);
	}
	
    protected final Set<C> clauses;
    
    public AbstractClauseBasedHeader(Collection<C> clauses) {
        if (clauses.isEmpty()) {
            throw new IllegalArgumentException("No clauses");
        }
        this.clauses = Collections.synchronizedSet(new HashSet<C>(clauses));
    }

    public AbstractClauseBasedHeader(String header, ClauseFactory<C> factory) {
    	this(computeClauses(header, factory));
    }

    @Override
    public final Collection<C> getClauses() {
        return Collections.unmodifiableCollection(clauses);
    }

    @Override
    public int hashCode() {
        int result = 17;
        result = 31 * result + getName().hashCode();
        result = 31 * result + clauses.hashCode();
        return result;
    }
    
    @Override
    public boolean equals(Object o) {
    	if (o == this) {
    		return true;
    	}
    	if (!(o instanceof AbstractClauseBasedHeader)) {
    		return false;
    	}
    	AbstractClauseBasedHeader<?> that = (AbstractClauseBasedHeader<?>)o;
    	return that.getName().equals(this.getName())
    			&& that.clauses.equals(this.clauses);
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        for (C clause : getClauses()) {
            builder.append(clause).append(',');
        }
        // Remove the trailing comma. Note at least one clause is guaranteed to
        // exist.
        builder.deleteCharAt(builder.length() - 1);
        return builder.toString();
    }
}
