/*******************************************************************************
 * Copyright (c) 2007, 2008 IBM Corporation
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 ******************************************************************************/
package org.eclipse.cosmos.me.internal.deployment.sdd.tooling.btg.rpm;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.Properties;
import java.util.Random;
import java.util.Vector;
import java.util.logging.Logger;

import org.eclipse.cosmos.me.provisional.deployment.sdd.common.spi.BaseFactory;
import org.eclipse.cosmos.me.provisional.deployment.sdd.common.spi.DeploymentDescriptor;
import org.eclipse.cosmos.me.provisional.deployment.sdd.common.spi.ManufacturerInformation;
import org.eclipse.cosmos.me.provisional.deployment.sdd.common.spi.PackageContent;
import org.eclipse.cosmos.me.provisional.deployment.sdd.common.spi.PackageDescriptor;
import org.eclipse.cosmos.me.provisional.deployment.sdd.common.spi.PackageIdentity;
import org.eclipse.cosmos.me.provisional.deployment.sdd.common.spi.Resource;
import org.eclipse.cosmos.me.provisional.deployment.sdd.common.spi.SPISession;
import org.eclipse.cosmos.me.provisional.deployment.sdd.common.spi.TranslatableString;
import org.eclipse.cosmos.me.provisional.deployment.sdd.common.spi.artifact.Artifact;
import org.eclipse.cosmos.me.provisional.deployment.sdd.common.spi.artifact.ArtifactFactory;
import org.eclipse.cosmos.me.provisional.deployment.sdd.common.spi.condition.ConditionFactory;
import org.eclipse.cosmos.me.provisional.deployment.sdd.common.spi.condition.VersionRange;
import org.eclipse.cosmos.me.provisional.deployment.sdd.common.spi.condition.VersionValue;
import org.eclipse.cosmos.me.provisional.deployment.sdd.common.spi.contentunit.ContentUnitFactory;
import org.eclipse.cosmos.me.provisional.deployment.sdd.common.spi.contentunit.InstallableUnit;
import org.eclipse.cosmos.me.provisional.deployment.sdd.common.spi.contentunit.ResultingResource;
import org.eclipse.cosmos.me.provisional.deployment.sdd.common.spi.requirement.Requirement;
import org.eclipse.cosmos.me.provisional.deployment.sdd.common.spi.requirement.RequirementFactory;
import org.eclipse.cosmos.me.provisional.deployment.sdd.common.spi.requirement.RequirementResourceConstraint;
import org.eclipse.cosmos.me.provisional.deployment.sdd.common.spi.requirement.VersionConstraint;



/**
 * 
 * @author Eric S. Rose (esrose@us.ibm.com)
 *
 */
public class RPMReader {
	private static final String SCHEMA_VERSION = "1.0";
	
	private RPMFile rpmFile = null;
	private File rpmFileHandle = null;
	private static Logger logger = Logger.getLogger("org.eclipse.cosmos.me.deployment.tooling.sdd.btg");//$NON-NLS-1$
    private static Logger consoleLogger = Logger.getLogger("org.eclipse.cosmos.me.deployment.tooling.sdd.btg.stdout");//$NON-NLS-1$
    
    private BaseFactory baseFactory = SPISession.DEFAULT_INSTANCE.createBaseFactory();
    private ContentUnitFactory contentUnitFactory = SPISession.DEFAULT_INSTANCE.createContentUnitFactory();
    private RequirementFactory requirementFactory = SPISession.DEFAULT_INSTANCE.createRequirementFactory();
    private ConditionFactory conditionFactory = SPISession.DEFAULT_INSTANCE.createConditionFactory();
    private ArtifactFactory artifactFactory = SPISession.DEFAULT_INSTANCE.createArtifactFactory();
    
	
	public RPMReader(String rpmFileName) throws MalformedURLException, IOException {
		rpmFileHandle = new File(rpmFileName); 
		rpmFile = new RPMFile(rpmFileHandle);		
	}
	
	public PackageDescriptor getPackageDescriptor() {
        /*
		 * PackageIdentityType
		 */
		PackageIdentity identity = baseFactory.createPackageIdentity();

        // Description
		String descriptionValue = rpmFile.getHeaderEntry(RPMFile.RPMTAG_DESCRIPTION).getStringData();
		TranslatableString description = new TranslatableString(descriptionValue);
		identity.setDescription(description);

		// ShortDescription
		String shortDescriptionValue = rpmFile.getHeaderEntry(RPMFile.RPMTAG_SUMMARY).getStringData();
		TranslatableString shortDescription = new TranslatableString(shortDescriptionValue);
		identity.setShortDescription(shortDescription);

		// Name
		String nameValue = rpmFile.getHeaderEntry(RPMFile.RPMTAG_NAME).getStringData();
		TranslatableString name = new TranslatableString(nameValue);
		identity.setName(name);
		
		// Version
		String version = rpmFile.getHeaderEntry(RPMFile.RPMTAG_VERSION).getStringData() + "-" +
						 rpmFile.getHeaderEntry(RPMFile.RPMTAG_RELEASE).getStringData();
		identity.setVersion(version);
		
		// BuildInformation
		String buildHost = rpmFile.getHeaderEntry(RPMFile.RPMTAG_BUILDHOST).getStringData();
		int[] buildTime = rpmFile.getHeaderEntry(RPMFile.RPMTAG_BUILDTIME).getIntData();
		if (buildHost != null && buildTime != null && buildTime.length > 0) {
			String buildID = buildHost + "_" + buildTime[0];
			identity.setBuildID(buildID);
		}
		
		// Manufacturer
		String manufacturerNameValue = rpmFile.getHeaderEntry(RPMFile.RPMTAG_VENDOR).getStringData();
		TranslatableString manufacturerName = new TranslatableString(manufacturerNameValue);
		ManufacturerInformation manufacturer = baseFactory.createManufacturerInformation(manufacturerName);
		identity.setManufacturerInformation(manufacturer);
		
		
		/*
		 * ContentsType
		 */
		Collection<PackageContent> contents = new Vector<PackageContent>();
        
        // Content
        String pathName = rpmFileHandle.getName();
        PackageContent content = baseFactory.createPackageContent("rpmFile", pathName);
        content.setContentPurpose("content");
        contents.add(content);
		

        // Create the PackageDescriptor
		PackageDescriptor pd = baseFactory.createPackageDescriptor(
				SCHEMA_VERSION, generateDescriptorID(), new Date(), identity, contents);
		
        return pd;
	}
	
	public DeploymentDescriptor getDeploymentDescriptor() {
		/*
		 * TopologyType
		 */
		Collection<Resource> topology = new ArrayList<Resource>();
		
		// Resource - Operating System
		Resource osResource = baseFactory.createResource("os", "OperatingSystem");
		// Name
		osResource.setName("linux");
		// Architecture
		Properties archProps = new Properties();
		archProps.put("arch", rpmFile.getHeaderEntry(RPMFile.RPMTAG_ARCH).getStringData());
		osResource.setProperties(archProps);
		// HostedResource
		String[] dependencies = rpmFile.getHeaderEntry(RPMFile.RPMTAG_REQUIRENAME).getStringArrayData();
		Collection<Resource> hostedResources = new ArrayList<Resource>();
		int requirementNumber = 1;
		for (int i = 0; i < dependencies.length; i++) {
			if (!ignoreRequirement(dependencies[i])) {
				Resource hostedResource = baseFactory.createHostedResource(
						dependencies[i] + "_" + requirementNumber++, "RequiredSoftware");
				hostedResource.setName(dependencies[i]);
				hostedResources.add(hostedResource);
				
				// Store this requirement's index in the REQUIRENAME array for later use
				Properties props = osResource.getProperties();
				props.put("rpmRequirenameIndex", String.valueOf(i));
				osResource.setProperties(props);
			}
		}
		osResource.setHostedResources(hostedResources);
		topology.add(osResource);
		
		// Resource - rpm Package Manager
		Resource rpmResource = baseFactory.createResource("rpm_pm", "PackageManager");
		rpmResource.setName("RPM Package Manager");
		topology.add(rpmResource);
		
		// Resource - package being installed
		Resource pkgResource = baseFactory.createResource(
				rpmFile.getHeaderEntry(RPMFile.RPMTAG_NAME).getStringData(), "InstallableResource");
		pkgResource.setName(rpmFile.getHeaderEntry(RPMFile.RPMTAG_NAME).getStringData());
		topology.add(pkgResource);
				
		
		/*
		 * InstallableUnitType
		 */
		// Requirements
		Collection<Requirement> requirements = new ArrayList<Requirement>();
		Requirement preReqs = requirementFactory.createRequirement("PreReqs");
		Collection<String> preReqsOperationList = new ArrayList<String>();
		preReqsOperationList.add("install");
		preReqs.setOperations(preReqsOperationList);
		requirements.add(preReqs);
		// Requirement - PreReqs
		int i = 1;
		Collection<RequirementResourceConstraint> preReqsConstraints = new ArrayList<RequirementResourceConstraint>();
		for (Resource resource : hostedResources) {
			// ResourceConstraint
			RequirementResourceConstraint constraint = requirementFactory.createRequirementResourceConstraint(
					"constraint_" + i++, resource.getResourceID());
			preReqsConstraints.add(constraint);
			
			// VersionConstraint
			String versionRequirement = findVersionRequirement(resource);
			if (versionRequirement != null) {
				VersionConstraint versionConstraint = requirementFactory.createVersionConstraint();

				// Greater than or equal to
				if (versionRequirement.startsWith(">=")) {
					versionRequirement = versionRequirement.substring(2);
					VersionRange versionRange =  conditionFactory.createVersionRange();
					versionRange.setMinimumVersion(versionRequirement);

					Collection<VersionRange> versionRanges = new ArrayList<VersionRange>();
					versionRanges.add(versionRange);
					versionConstraint.setVersionRanges(versionRanges);
				}
				// Equal
				if (versionRequirement.startsWith("=")) {
					versionRequirement = versionRequirement.substring(1);
					VersionValue versionValue = conditionFactory.createVersionValue(versionRequirement);
					
					Collection<VersionValue> versionValues = new ArrayList<VersionValue>();
					versionValues.add(versionValue);
					versionConstraint.setVersionValues(versionValues);					
				}
				// Less than / less than or equals to
				if (versionRequirement.startsWith("<")) {
					boolean includeEqualTo = false;
					versionRequirement = versionRequirement.substring(1);
					if (versionRequirement.startsWith("=")) {
						versionRequirement = versionRequirement.substring(1);
						includeEqualTo = true;
					}
					
					versionRequirement = versionRequirement.substring(1);
					VersionRange versionRange = conditionFactory.createVersionRange();
					versionRange.setMaximumVersion(versionRequirement);
					versionRange.setMaximumVersionInclusiveness(includeEqualTo);
					
					Collection<VersionRange> versionRanges = new ArrayList<VersionRange>();
					versionRanges.add(versionRange);
					versionConstraint.setVersionRanges(versionRanges);					
				}
				
				Collection<VersionConstraint> versionConstraints = new ArrayList<VersionConstraint>();
				versionConstraints.add(versionConstraint);
				constraint.setVersionConstraintArray(versionConstraints);
			}
		}
		preReqs.setResourceConstraints(preReqsConstraints);
		
		// Requirement - RPM
		Requirement rpmReqs = requirementFactory.createRequirement("PackageManager");
		Collection<String> rpmReqsOperationList = new ArrayList<String>();
		rpmReqsOperationList.add("install");
		rpmReqs.setOperations(rpmReqsOperationList);
		requirements.add(rpmReqs);
		// RPM ResourceConstraint
		RequirementResourceConstraint rpmConstraint = requirementFactory.createRequirementResourceConstraint(
				"rpmConstraint", rpmResource.getResourceID());
		Collection<RequirementResourceConstraint> rpmReqsConstraints = new ArrayList<RequirementResourceConstraint>();
		rpmReqsConstraints.add(rpmConstraint);
		// RPM VersionConstraint
		VersionConstraint rpmVersionConstraint = requirementFactory.createVersionConstraint();
		VersionRange versionRange = conditionFactory.createVersionRange();		
		versionRange.setMinimumVersion("3.0");
		Collection<VersionRange> versionRanges = new ArrayList<VersionRange>();
		versionRanges.add(versionRange);
		rpmVersionConstraint.setVersionRanges(versionRanges);
		
		rpmReqs.setResourceConstraints(rpmReqsConstraints);
		
		
		// ResultingResource
		ResultingResource resultingResource = contentUnitFactory.createResultingResource(pkgResource.getResourceID());
		resultingResource.setVersion(rpmFile.getHeaderEntry(RPMFile.RPMTAG_VERSION).getStringData() + "-" +
				 					 rpmFile.getHeaderEntry(RPMFile.RPMTAG_RELEASE).getStringData());
		Collection<ResultingResource> resultingResources = new ArrayList<ResultingResource>();
		
		// Artifacts
		Artifact installArtifact = artifactFactory.createInstallArtifact();
		installArtifact.setContentReference("rpmFile");
		installArtifact.setPackageType("rpm");
		
		// Create the InstallableUnit
		InstallableUnit installableUnit = contentUnitFactory.createInstallableUnit(
				resultingResource.getResourceReference() + "_IU", rpmResource.getResourceID(), installArtifact,
				ContentUnitFactory.INSTALL_ARTIFACT);
		installableUnit.setRequirements(requirements);
		installableUnit.setResultingResources(resultingResources);
		
		
		// Create the DeploymentDescriptor
		DeploymentDescriptor dd = baseFactory.createDeploymentDescriptor(
				SCHEMA_VERSION, generateDescriptorID(), new Date(), installableUnit, topology);
		
		return dd;
	}
	
	/*
	 * Ignore certain requirements known to be extraneous
	 */
	private boolean ignoreRequirement(String requirement) {
		String[] requirements = rpmFile.getHeaderEntry(RPMFile.RPMTAG_REQUIRENAME).getStringArrayData();

		// Ignore lsb
		if (requirement.equals("lsb")) {
			return (true);
		}
		// Ignore rpmlib(VersionedDependencies)
		if (requirement.equals("rpmlib(VersionedDependencies)")) {
			return (true);
		}
		// Ignore rpmlib(PayloadFilesHavePrefix)
		if (requirement.equals("rpmlib(PayloadFilesHavePrefix)")) {
			return (true);
		}
		// Ignore rpmlib(CompressedFileNames)
		if (requirement.equals("rpmlib(CompressedFileNames)")) {
			return (true);
		}
		// Ignore /bin/sh
		if (requirement.equals("/bin/sh")) {
			return (true);
		}
		// Ignore config(pkgname) - example: config(vim-minimal)
		String configReq = "config(" + rpmFile.getHeaderEntry(RPMFile.RPMTAG_NAME).getStringData() + ")"; 
		if (requirement.equals(configReq)) {
			return (true);
		}		
		// Ignore library(version) - example: libc.so.6(GLIBC_2.0)
		if (requirement.indexOf("(") != -1) {
			String libName = requirement.substring(0, requirement.indexOf("("));
			
			for (int j = 0; j < requirements.length; j++) {
				if (libName.equals(requirements[j])) {
					return (true);
				}
			}
		}
		
		return (false);
	}
	
	/*
	 * Find the version requirement for a given requirement
	 * Returns null if there's no version associated with the given requirement
	 * 
	 * Side effect: Removes the rpmRequirementIndex property from the given requirement
	 */
	private String findVersionRequirement(Resource requirement) {
		Iterator<Object> keysIter = requirement.getProperties().keySet().iterator();
		String[] versionRequirements = rpmFile.getHeaderEntry(RPMFile.RPMTAG_REQUIREVERSION).getStringArrayData();
		int[] versionFlags = rpmFile.getHeaderEntry(RPMFile.RPMTAG_REQUIREFLAGS).getIntData();
		int index = -1;
		
		// Find the index of the given requirement
		Properties properties = requirement.getProperties();
		while (keysIter.hasNext()) {
			String key = (String)keysIter.next();
			
			if (key.equals("rpmRequirenameIndex")) {
				index = Integer.parseInt(properties.getProperty(key));
				keysIter.remove();
			}
		}
		
		String version = versionRequirements[index];
				
		if (!version.equals("")) {
			if ((versionFlags[index] & RPMFile.RPMSENSE_EQUAL) == RPMFile.RPMSENSE_EQUAL) {
				version = "=" + version;
			}
			if ((versionFlags[index] & RPMFile.RPMSENSE_LESS) == RPMFile.RPMSENSE_LESS) {
				version = "<" + version;
			}					
			if ((versionFlags[index] & RPMFile.RPMSENSE_GREATER) == RPMFile.RPMSENSE_GREATER) {
				version = ">" + version;
			}

			// Return the version number along with flags (<, >, =)
			return (version);
		}
		else {
			return (null);
		}
	}
	
	/*
	 * Generate a valid, random descriptorID
	 */
	private byte[] generateDescriptorID() {
		// Descriptor is a 16 element array of bytes
		byte[] descriptorID = new byte[16];
		// Generate a random array of bytes
		Random random = new Random(Calendar.getInstance().getTimeInMillis());
		random.nextBytes(descriptorID);
		
		return (descriptorID);
	}
}