/*
 * 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.osgi.framework.Constants.BUNDLE_VERSION_ATTRIBUTE;
import static org.osgi.framework.Constants.VERSION_ATTRIBUTE;

import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.aries.subsystem.modelling.DeployedBundles;
import org.apache.aries.subsystem.modelling.ImportedBundle;
import org.apache.aries.subsystem.modelling.ImportedPackage;
import org.apache.aries.subsystem.modelling.InvalidAttributeException;
import org.apache.aries.subsystem.modelling.ModelledResource;
import org.apache.aries.subsystem.modelling.ModellingConstants;
import org.apache.aries.subsystem.modelling.ModellingHelper;
import org.apache.aries.subsystem.modelling.Provider;
import org.apache.aries.util.VersionRange;
import org.apache.aries.util.manifest.ManifestHeaderProcessor;
import org.osgi.framework.Constants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ModellingHelperImpl implements ModellingHelper {
  private static final  Logger logger = LoggerFactory.getLogger(ModellingHelperImpl.class);

  
  public boolean areMandatoryAttributesPresent(
      Map<String, String> consumerAttributes, Provider p) {
    return areMandatoryAttributesPresent_(consumerAttributes, p);
  }

  public ImportedBundle buildFragmentHost(String fragmentHostHeader)
      throws InvalidAttributeException {
    return buildFragmentHost_(fragmentHostHeader);
  }

  public ImportedPackage intersectPackage(ImportedPackage p1, ImportedPackage p2) {
    return intersectPackage_(p1, p2);
  }
  
  public DeployedBundles createDeployedBundles(String assetName,
      Collection<ImportedBundle> appContentNames,
      Collection<ImportedBundle> appUseBundleNames,
      Collection<ModelledResource> fakeServiceProvidingBundles) 
  {
    logger.debug("Method entry: {}, args {}", "createDeployedBundles", new Object[]{assetName,
        appContentNames, appUseBundleNames, fakeServiceProvidingBundles});  
    DeployedBundles result = new DeployedBundlesImpl (assetName, 
        appContentNames, appUseBundleNames, fakeServiceProvidingBundles);
    logger.debug("Method exit: {}, returning {}", "createDeployedBundles", result);
    return result;
  }
  
  // These underlying static methods are directly accessible 
  // from other classes within the bundle
  public static boolean areMandatoryAttributesPresent_(Map<String,String> consumerAttributes, Provider p) {
    logger.debug("Method entry: {}, args {}", "areMandatoryAttributesPresent_", new Object[]{consumerAttributes, p});
    boolean allPresent = true;
    String mandatory = (String) p.getAttributes().get(Constants.MANDATORY_DIRECTIVE + ":");
    
    if(mandatory != null && !mandatory.isEmpty()) {
      List<String> attributeNames = ManifestHeaderProcessor.split(mandatory, ",");
      
      for(String name : attributeNames) {
        allPresent = consumerAttributes.containsKey(name);
        if(!allPresent)
          break;
      }
    }
    logger.debug("Method exit: {}, returning {}", "areMandatoryAttributesPresent_", allPresent);
    return allPresent;
  }
  
  
  
  public static ImportedBundle buildFragmentHost_(String fragmentHostHeader) throws InvalidAttributeException {
    logger.debug("Method entry: {}, args {}", "buildFragmentHost_", new Object[]{fragmentHostHeader});
    if(fragmentHostHeader == null) { 
      
      return null;
    }
    Map<String, Map<String, String>> parsedFragHost = ManifestHeaderProcessor.parseImportString(fragmentHostHeader);
    if(parsedFragHost.size() != 1)
      throw new InvalidAttributeException(MessageUtil.getMessage("MORE_THAN_ONE_FRAG_HOST",
          new Object[] {fragmentHostHeader}, 
          "An internal error occurred. A bundle fragment manifest must define exactly one Fragment-Host entry. The following entry was found" + fragmentHostHeader + "."));
    
    String hostName = parsedFragHost.keySet().iterator().next();
    Map<String, String> attribs = parsedFragHost.get(hostName);
    
    String bundleVersion = attribs.remove(BUNDLE_VERSION_ATTRIBUTE);
    if (bundleVersion != null && attribs.get(VERSION_ATTRIBUTE) == null) { 
      attribs.put (VERSION_ATTRIBUTE, bundleVersion);
    }
    attribs.put(ModellingConstants.OBR_SYMBOLIC_NAME, hostName);  
    
    String filter = ManifestHeaderProcessor.generateFilter(attribs);
    
    ImportedBundle result = new ImportedBundleImpl(filter, attribs);
    logger.debug("Method exit: {}, returning {}", "buildFragmentHost_", result);
    return result;
  }
  
  public static ImportedPackage intersectPackage_ (ImportedPackage p1, ImportedPackage p2) { 
    
    logger.debug("Method entry: {}, args {}", "intersectPackage_", new Object[]{p1, p2});
    ImportedPackage result = null;
    if (p1.getPackageName().equals(p2.getPackageName()))
    {
      Map<String,String> att1 = new HashMap<String, String>(p1.getAttributes());
      Map<String,String> att2 = new HashMap<String, String>(p2.getAttributes());
      
      // Get the versions, we remove them so that the remaining attributes can be matched.
      String rangeStr1 = att1.remove(VERSION_ATTRIBUTE);
      String rangeStr2 = att2.remove(VERSION_ATTRIBUTE);

      //Also remove the optional directive as we don't care about that either
      att1.remove(OPTIONAL_KEY);
      att2.remove(OPTIONAL_KEY);
      
      //If identical take either, otherwise null!
      Map<String, String> mergedAttribs = att1.equals(att2) ? att1 : null;
      if (mergedAttribs == null)
      {
        // Cannot intersect requirements if attributes are not identical.
        result = null;
      }
      else
      {
        boolean isIntersectSuccessful = true;
        
        if (rangeStr1 != null && rangeStr2 != null)
        {
          // Both requirements have a version constraint so check for an intersection between them.
          VersionRange range1 = ManifestHeaderProcessor.parseVersionRange(rangeStr1);
          VersionRange range2 = ManifestHeaderProcessor.parseVersionRange(rangeStr2);
          VersionRange intersectRange = range1.intersect(range2);
          
          if (intersectRange == null)
          {
            // No intersection possible.
            isIntersectSuccessful = false;
          }
          else
          {
            // Use the intersected version range.
            mergedAttribs.put(VERSION_ATTRIBUTE, intersectRange.toString());
          }
        }
        else if (rangeStr1 != null)
        {
          mergedAttribs.put(VERSION_ATTRIBUTE, rangeStr1);
        }
        else if (rangeStr2 != null)
        {
          mergedAttribs.put(VERSION_ATTRIBUTE, rangeStr2);
        }
        
        //If both optional, we are optional, otherwise use the default
        if(p1.isOptional() && p2.isOptional()) 
        {
          mergedAttribs.put(OPTIONAL_KEY, Constants.RESOLUTION_OPTIONAL);
        } 
        
        try { 
          result = (isIntersectSuccessful ? new ImportedPackageImpl(p1.getPackageName(), mergedAttribs) : null);
        } catch (InvalidAttributeException iax) { 
          logger.error(iax.getMessage());
        }
      }
    } 
    logger.debug("Method exit: {}, returning {}", "intersectPackage_", result);
    return result;
  }
  
}
