/*
 * 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 WARRANTIESOR 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.modelling.impl;

import static org.apache.aries.subsystem.modelling.ModellingConstants.OPTIONAL_KEY;
import static org.apache.aries.subsystem.modelling.ResourceType.PACKAGE;

import java.util.Collections;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.aries.subsystem.modelling.ImportedPackage;
import org.apache.aries.subsystem.modelling.InvalidAttributeException;
import org.apache.aries.subsystem.modelling.Provider;
import org.apache.aries.subsystem.modelling.ResourceType;
import org.apache.aries.util.manifest.ManifestHeaderProcessor;
import org.osgi.framework.Constants;
import org.osgi.framework.Filter;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.Version;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
 * An imported, or required package. Capable of generating an entry in DEPLOYMENT.MF's Import-Package header. 
 */
public class ImportedPackageImpl implements ImportedPackage
{

  
  private final boolean _optional;
  private final String _filterString;
  private final Filter _filter;
  private final String _package;
  private final String _versionRange;
  private final Map<String,String> _attributes;
  private final Logger logger = LoggerFactory.getLogger(ImportedPackageImpl.class);
  /**
   * Construct a package requirement
   * @param pkg The name of the required package
   * @param attributes Other attributes - most commonly, version
   * @throws InvalidAttributeException
   */
  public ImportedPackageImpl (String pkg, Map<String, String> attributes) throws InvalidAttributeException {
    logger.debug("Method entry: {}, args {}", "ImportedPackageImpl", new Object[] {pkg, attributes});
    _package = pkg;
    String versionRange = null;
    if (attributes != null) {
      _optional = Constants.RESOLUTION_OPTIONAL.equals(attributes.get(OPTIONAL_KEY));
      versionRange = attributes.get(Constants.VERSION_ATTRIBUTE);
      _attributes = new HashMap<String, String>(attributes);
    } else { 
      _optional = false;
      _attributes = new HashMap<String, String>();
    }
    if (versionRange == null) {
      _versionRange = Version.emptyVersion.toString();
    } else { 
      _versionRange = versionRange;
    }
    
    _attributes.put(Constants.VERSION_ATTRIBUTE, _versionRange);
    _filterString = ManifestHeaderProcessor.generateFilter(PACKAGE.toString(), _package, _attributes);
    try { 
    _filter = FrameworkUtil.createFilter(FilterUtils.removeMandatoryFilterToken(_filterString));
    } catch (InvalidSyntaxException isx) { 
      logger.debug("Method exit: {}, returning {}", "ImportedPackageImpl", new Object[] {isx});
      throw new InvalidAttributeException(isx);
    }
    logger.debug("Method exit: {}, returning {}", "ImportedPackageImpl");
  }
  
  /**
   * Get this ImportedPackageImpl's attributes
   * @return attributes
   */
  public Map<String, String> getAttributes() { 
    logger.debug("Method entry: {}, args {}", "getAttributes");
    logger.debug("Method exit: {}, returning {}", "getAttributes", new Object[] {_attributes});
    return Collections.unmodifiableMap(_attributes);
  }
  
  /**
   * Get the package name
   * @return package name
   */
  public String getPackageName() { 
    logger.debug("Method entry: {}, args {}", "getPackageName");
    logger.debug("Method exit: {}, returning {}", "getPackageName", new Object[] {_package});
    return _package;
  }
  
  /**
   * Get the imported package's version range
   * @return version range
   */
  public String getVersionRange() {
    logger.debug("Method entry: {}, args {}", "getVersionRange");
    logger.debug("Method exit: {}, returning {}", "getVersionRange", new Object[] {_versionRange});
    return _versionRange;
  }
  

  public String getAttributeFilter() {
    logger.debug("Method entry: {}, args {}", "getAttributeFilter");
    logger.debug("Method exit: {}, returning {}", "getAttributeFilter", new Object[] {_filterString});
    return _filterString;
  }


  public ResourceType getType() {
    logger.debug("Method entry: {}, args {}", "getType");
    logger.debug("Method exit: {}, returning {}", "getType", new Object[] {PACKAGE});
    return PACKAGE;
  }


  public boolean isMultiple() {
    logger.debug("Method entry: {}, args {}", "isMultiple");
    logger.debug("Method exit: {}, returning {}", "isMultiple", new Object[] {false});
    return false;   // cannot import a given package more than once
  }


  public boolean isOptional() {
    logger.debug("Method entry: {}, args {}", "isOptional");
    logger.debug("Method exit: {}, returning {}", "isOptional", new Object[] {_optional});
    return _optional;
  }


  public boolean isSatisfied(Provider capability) {
    logger.debug("Method entry: {}, args {}", "isSatisfied", new Object[]{capability});
    if (capability.getType() != PACKAGE) { 
      logger.debug("Method exit: {}, returning {}", "isSatisfied", new Object[] {false});
      return false;
    }
    Dictionary<String, Object> dict = new Hashtable<String, Object> (capability.getAttributes());
    String version = (String) dict.get(Constants.VERSION_ATTRIBUTE);
    if (version != null) { 
      dict.put(Constants.VERSION_ATTRIBUTE, Version.parseVersion(version));
    }
    
    boolean allPresent = ModellingHelperImpl.areMandatoryAttributesPresent_(_attributes, capability);
    boolean result = allPresent && _filter.match(dict);
    logger.debug("Method exit: {}, returning {}", "isSatisfied", new Object[] {result});
    return result;
  }

  
  
  /**
  * This method turns an {@link ImportedPackageImpl} into a string suitable for a 
  * Provision-Bundle style package import.  
  * It will not include ;bundle-symbolic-name=bundleName;bundle-version=version attribute pairs
  * @return A String
  */
  @SuppressWarnings("deprecation")
  public String toDeploymentString() {
    logger.debug("Method entry: {}, args {}", "toDeploymentString");
    StringBuilder sb = new StringBuilder(_package);
    // Note that the RESOLUTION_DIRECTIVE is set in this map, so it will be
    // output automatically. p41 of the OSGi Core Spec v4.2 includes an example
    // Import-Package with a resolution:=mandatory directive on. We could choose to suppress
    // resolution:=mandatory on packages however, since mandatory is the default.
    for (Map.Entry<String, String> entry : _attributes.entrySet()) {
      String key = entry.getKey();
      String value = entry.getValue();
      if (!key.equals(Constants.BUNDLE_SYMBOLICNAME_ATTRIBUTE)
          && !key.equals(Constants.BUNDLE_VERSION_ATTRIBUTE)
          && !key.equals(PACKAGE.toString())
          && !key.equals(Constants.PACKAGE_SPECIFICATION_VERSION)) {
        sb.append(";").append(key).append("=\"").append(value).append('"');
      } else {
        logger.debug("ignoring attribute {" + key + "=" + value + "} in ImportedPackageImpl.toDeploymentString()");
      }
    }
    String result = sb.toString();
    logger.debug("Method exit: {}, returning {}", "toDeploymentString", new Object[]{result});
    return result;
  }
  
  @Override
  public String toString() {
    return toDeploymentString();
  }
  
	@Override
	public boolean equals(Object thing) {
		if (thing == this) {
			return true;
		} else {
			if (thing instanceof ImportedPackage) {
				ImportedPackage otherPackage = (ImportedPackage) thing;
				if (!this.getPackageName()
						.equals(otherPackage.getPackageName())) {
					return false;
				}
				if (!this.getVersionRange().equals(otherPackage.getVersionRange())) {
					return false;
				}
				if (!this.getPackageName()
						.equals(otherPackage.getPackageName())) {
					return false;
				}
				Map<String, String> otherAttributes = otherPackage
						.getAttributes();

				// Ignore the bundle, since the same package imported from
				// different bundles should count as the same

				if (!attributesAreEqual(otherAttributes)) {
					return false;
				}
			}
			return true;

		}
	}

	private boolean attributesAreEqual(Map<String, String> otherAttributes) {
		// We better have the same number of attributes
		if (this.getAttributes().size() != otherAttributes.size()) {
			return false;
		}

		for (Entry<String, String> entry : getAttributes().entrySet()) {
			String key = entry.getKey();
			if (otherAttributes != null && otherAttributes.containsKey(key)) {
				Object otherValue = otherAttributes.get(key);
				Object value = entry.getValue();
				if (value == null) {
					if (otherValue != null) {
						return false;
					}
				} else {
					if (!value.equals(otherValue)) {
						return false;
					}
				}
			} else {
				// We insist every value be present on both sides
				return false;
			}
		}

		return true;
	}
  
  @Override
  public int hashCode()
  {
	  return getPackageName().hashCode();
  }
}