/*
 * RHQ Management Platform
 * Copyright (C) 2005-2008 Red Hat, Inc.
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation version 2 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
package org.rhq.enterprise.server.plugins.rhnhosted;

import java.io.Serializable;
import java.util.List;

import javax.xml.bind.JAXBElement;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jdom.Element;
import org.jdom.Namespace;
import org.jdom.output.XMLOutputter;

import org.rhq.enterprise.server.plugins.rhnhosted.xml.RhnPackageObsoletesEntryType;
import org.rhq.enterprise.server.plugins.rhnhosted.xml.RhnPackageObsoletesType;
import org.rhq.enterprise.server.plugins.rhnhosted.xml.RhnPackageProvidesEntryType;
import org.rhq.enterprise.server.plugins.rhnhosted.xml.RhnPackageProvidesType;
import org.rhq.enterprise.server.plugins.rhnhosted.xml.RhnPackageRequiresEntryType;
import org.rhq.enterprise.server.plugins.rhnhosted.xml.RhnPackageRequiresType;
import org.rhq.enterprise.server.plugins.rhnhosted.xml.RhnPackageType;

/**
 * @author John Matthews
 */
public class PrimaryXML {
    /** Example package element from primary.xml used by yum
     *

    <package type="rpm">
      <name>gnome-user-docs</name>
      <arch>noarch</arch>
      <version ver="2.16.0" rel="2.fc6" epoch="0"/>
      <checksum type="md5" pkgid="YES">4e7d379301407b4c79f211596b802a13</checksum>
      <summary>GNOME User Documentation</summary>
      <description>This package contains end user documentation for the GNOME desktop environment.</description>
      <packager/>
      <url/>
      <time file="1157750823" build="1157750823"/>
      <size package="6549456" archive="11858600" installed=""/>
      <location href="getPackage/gnome-user-docs-2.16.0-2.fc6.noarch.rpm"/>
      <format>
          <rpm:license>FDL</rpm:license>
          <rpm:vendor>Red Hat, Inc.</rpm:vendor>
          <rpm:group>Documentation</rpm:group>
          <rpm:buildhost>altix2.build.redhat.com</rpm:buildhost>
          <rpm:sourcerpm>gnome-user-docs-2.16.0-2.fc6.src.rpm</rpm:sourcerpm>
          <rpm:header-range start="440" end="97656"/>
          <rpm:provides>
              <rpm:entry name="gnome-user-docs" flags="EQ" epoch="0" ver="2.16.0" rel="2.fc6"/>
          </rpm:provides>
          <rpm:requires>
              <rpm:entry name="/bin/sh"/>
              <rpm:entry name="/bin/sh"/>
              <rpm:entry name="rpmlib(CompressedFileNames)" flags="LE" epoch="0" ver="3.0.4" rel="1"/>
              <rpm:entry name="rpmlib(PayloadFilesHavePrefix)" flags="LE" epoch="0" ver="4.0" rel="1"/>
              <rpm:entry name="rpmlib(VersionedDependencies)" flags="LE" epoch="0" ver="3.0.3" rel="1"/>
              <rpm:entry name="scrollkeeper" flags="GE" epoch="0" ver="0.3.11"/>
          </rpm:requires>
          <rpm:conflicts/>
          <rpm:obsoletes>
              <rpm:entry name="gnome-users-guide"/>
          </rpm:obsoletes>
      </format>
    </package>
    
       **/

    /** RPM uses bit flags to determine if a package relationships is "Greater, LessThan, Equals";
     * These values can be found from running the python console and executing
     * "import rpm"  "print rpm.RPMSENSE_LESS", "print rpm.RPMSENSE_GREATER", "print rpm.RPMSENSE_EQUAL"
     */
    final static int RPM_SENSE_LESS = 2;
    final static int RPM_SENSE_GREATER = 4;
    final static int RPM_SENSE_EQUAL = 8;

    final static String RHNHOSTED_URI = "http://rhq-project.org/rhnhosted";

    static private final Log log = LogFactory.getLog(PrimaryXML.class);

    static public String getFlags(String sense) {
        int tmp = Integer.parseInt(sense);
        int flags = tmp & (RPM_SENSE_LESS | RPM_SENSE_GREATER | RPM_SENSE_EQUAL);
        if (flags == (RPM_SENSE_LESS | RPM_SENSE_EQUAL)) {
            return "LE";
        } else if (flags == RPM_SENSE_LESS) {
            return "LT";
        } else if (flags == (RPM_SENSE_GREATER | RPM_SENSE_EQUAL)) {
            return "GE";
        } else if (flags == (RPM_SENSE_GREATER)) {
            return "GT";
        } else if (flags == (RPM_SENSE_EQUAL)) {
            return "EQ";
        }
        return "";
    }

    /**
     * Will return the 'epoch'.  If the epoch is empty, then a "0" is returned
     * to comply with yum's expectations for package metadata
     * @param version  string containing epoch:version-release
     * @return
     */
    static public String getEpoch(String version) {
        int end = version.indexOf("-");
        if (end < 0) {
            return "0";
        }
        String tmp = version.substring(0, end);
        int index = tmp.indexOf(":");
        if (index < 0) {
            return "0";
        }
        String epoch = tmp.substring(0, index);
        if (StringUtils.isBlank(epoch)) {
            return "0";
        }
        return epoch;
    }

    static public String getVersion(String version) {
        int end = version.indexOf("-");
        if (end < 0) {
            return version; //unsure how to parse, return passed in string
        }
        String tmp = version.substring(0, end);
        int start = tmp.indexOf(":");
        if (start < 0) {
            start = 0;
        } else {
            start = start + 1;
        }
        return tmp.substring(start, end);
    }

    static public String getRelease(String version) {
        int start = version.indexOf("-");
        if (start < 0) {
            return "";
        }
        start = start + 1; // go past the "-" character
        return version.substring(start);
    }

    /**
     * Will create a xml string snippet conforming to the <package> entry in a primary.xml file used by yum
     *
     * @param pkg JAXB Object to transform
     * @return string snippet of xml data
     */
    static public String createPackageXML(RhnPackageType pkg) {
        Namespace packageNS = Namespace.getNamespace(RHNHOSTED_URI);

        Element top = new Element("package", packageNS);
        top.setAttribute("type", "rpm");

        Element name = new Element("name", packageNS);
        name.setText(pkg.getName());
        top.addContent(name);

        Element arch = new Element("arch", packageNS);
        arch.setText(pkg.getPackageArch());
        top.addContent(arch);

        Element version = new Element("version", packageNS);
        version.setText(pkg.getVersion());
        version.setAttribute("ver", pkg.getVersion());
        version.setAttribute("rel", pkg.getRelease());
        String epoch = pkg.getEpoch();
        // Note, if epoch is empty we need to change it to a zero as that is what yum expects.
        if (StringUtils.isBlank(epoch)) {
            epoch = "0";
        }
        version.setAttribute("epoch", epoch);
        top.addContent(version);

        Element checksum = new Element("checksum", packageNS);
        checksum.setAttribute("type", "md5");
        checksum.setAttribute("pkgid", "YES");
        checksum.setText(pkg.getMd5Sum());
        top.addContent(checksum);

        Element summary = new Element("summary", packageNS);
        summary.setText(pkg.getRhnPackageSummary());
        top.addContent(summary);

        Element description = new Element("description", packageNS);
        description.setText(pkg.getRhnPackageDescription());
        top.addContent(description);

        Element packager = new Element("packager", packageNS);
        //TODO: Not sure if we get 'packager' info from RHN.
        packager.setText("");
        top.addContent(packager);

        Element url = new Element("url", packageNS);
        //TODO: Not sure what to put for url value, don't think it applies to RHN
        url.setText("");
        top.addContent(url);

        Element time = new Element("time", packageNS);
        //TODO: Verify below, guessing for file/build times.
        time.setAttribute("file", pkg.getLastModified());
        time.setAttribute("build", pkg.getBuildTime());
        top.addContent(time);

        Element size = new Element("size", packageNS);
        size.setAttribute("package", pkg.getPackageSize());
        size.setAttribute("archive", pkg.getPayloadSize());
        //TODO: Unsure about installed, does this need to change on server side when package is installed on a client?
        size.setAttribute("installed", "");
        top.addContent(size);

        Element location = new Element("location", packageNS);
        //This value can not be empty and can not contain a "?".
        //It's value is ignored by the RHQ processing for yum.  
        //RHQ will append a series of request parameters to download the file.
        String rpmName = RHNHelper.constructRpmDownloadName(pkg.getName(), pkg.getVersion(), pkg.getRelease(), pkg.getEpoch(),
            pkg.getPackageArch());
        location.setAttribute("href", rpmName);
        top.addContent(location);

        Element format = new Element("format", packageNS);

        Namespace rpmNS = Namespace.getNamespace("rpm", "http://rhq-project.org/rhnhosted");
        Element rpmLicense = new Element("license", rpmNS);
        rpmLicense.setText(pkg.getRhnPackageCopyright());
        format.addContent(rpmLicense);

        Element rpmVendor = new Element("vendor", rpmNS);
        rpmVendor.setText(pkg.getRhnPackageVendor());
        format.addContent(rpmVendor);

        Element rpmGroup = new Element("group", rpmNS);
        rpmGroup.setText(pkg.getPackageGroup());
        format.addContent(rpmGroup);

        Element rpmBuildHost = new Element("buildhost", rpmNS);
        rpmBuildHost.setText(pkg.getBuildHost());
        format.addContent(rpmBuildHost);

        Element rpmSourceRPM = new Element("sourcerpm", rpmNS);
        rpmSourceRPM.setText(pkg.getSourceRpm());
        format.addContent(rpmSourceRPM);

        Element rpmHeaderRange = new Element("header-range", rpmNS);
        rpmHeaderRange.setAttribute("start", pkg.getRhnPackageHeaderStart());
        rpmHeaderRange.setAttribute("end", pkg.getRhnPackageHeaderEnd());
        format.addContent(rpmHeaderRange);

        Element rpmProvides = new Element("provides", rpmNS);
        RhnPackageProvidesType provides_type = pkg.getRhnPackageProvides();
        if (provides_type != null) {
            List<RhnPackageProvidesEntryType> provides = provides_type.getRhnPackageProvidesEntry();
            for (RhnPackageProvidesEntryType provEntry : provides) {
                Element entry = new Element("entry", rpmNS);
                entry.setAttribute("name", provEntry.getName());
                String flags = getFlags(provEntry.getSense());
                if (!StringUtils.isBlank(flags)) {
                    entry.setAttribute("flags", flags);
                    String provEpoch = getEpoch(provEntry.getVersion());
                    entry.setAttribute("epoch", provEpoch);
                    String provVer = getVersion(provEntry.getVersion());
                    entry.setAttribute("ver", provVer);
                    String provRel = getRelease(provEntry.getVersion());
                    entry.setAttribute("rel", getRelease(provEntry.getVersion()));
                }
                rpmProvides.addContent(entry);
            }
        }
        format.addContent(rpmProvides);

        Element rpmRequires = new Element("requires", rpmNS);
        RhnPackageRequiresType requires_type = pkg.getRhnPackageRequires();
        if (requires_type != null) {
            List<RhnPackageRequiresEntryType> requires = requires_type.getRhnPackageRequiresEntry();
            for (RhnPackageRequiresEntryType reqEntry : requires) {
                Element entry = new Element("entry", rpmNS);
                entry.setAttribute("name", reqEntry.getName());
                String flags = getFlags(reqEntry.getSense());
                if (!StringUtils.isBlank(flags)) {
                    entry.setAttribute("flags", flags);
                    String reqEpoch = getEpoch(reqEntry.getVersion());
                    entry.setAttribute("epoch", reqEpoch);
                    String reqVer = getVersion(reqEntry.getVersion());
                    entry.setAttribute("ver", reqVer);
                    String reqRel = getRelease(reqEntry.getVersion());
                    entry.setAttribute("rel", getRelease(reqEntry.getVersion()));
                }
                rpmRequires.addContent(entry);
            }
        }
        format.addContent(rpmRequires);

        Element rpmConflicts = new Element("conflicts", rpmNS);
        rpmConflicts.setText(pkg.getRhnPackageConflicts());
        format.addContent(rpmConflicts);

        Element rpmObsoletes = new Element("obsoletes", rpmNS);
        RhnPackageObsoletesType obs_type = pkg.getRhnPackageObsoletes();
        if (obs_type != null) {
            List<Serializable> obsoletes = obs_type.getContent();
            for (Serializable s : obsoletes) {
                RhnPackageObsoletesEntryType obsEntry = null;
                if (s instanceof String) {
                    String obsString = (String) s;
                    if (StringUtils.isBlank(obsString)) {
                        continue;
                    }
                    //log.debug("Adding Obsoletes info <String Class> value = " + obsString);
                    Element entry = new Element("entry", rpmNS);
                    entry.setAttribute("name", obsString);
                    rpmObsoletes.addContent(entry);
                    // skip rest of obs processing for this entry
                    continue;
                }
                //
                // Below obs entry processing is for JAXBElement types only
                //
                if (s instanceof JAXBElement) {
                    JAXBElement je = (JAXBElement) s;
                    //log.debug("Processing obsolete info for JAXBElement of type : " + je.getDeclaredType());
                    obsEntry = (RhnPackageObsoletesEntryType) je.getValue();
                } else if (s instanceof RhnPackageObsoletesEntryType) {
                    obsEntry = (RhnPackageObsoletesEntryType) s;
                } else {
                    log.info("Processing obsoletes info:  unable to determine what class obsoletes entry is: "
                        + "getClass() =  " + s.getClass() + ", toString() = " + s.toString() + ", hashCode = "
                        + s.hashCode());
                    continue;
                }
                Element entry = new Element("entry", rpmNS);
                entry.setAttribute("name", obsEntry.getName());
                String obsVer = obsEntry.getVersion();
                if (!StringUtils.isBlank(obsVer)) {
                    entry.setAttribute("version", obsVer);
                }
                String obsFlags = getFlags(obsEntry.getSense());
                if (!StringUtils.isBlank(obsFlags)) {
                    entry.setAttribute("flags", obsFlags);
                }
                rpmObsoletes.addContent(entry);
            }
        }
        format.addContent(rpmObsoletes);

        top.addContent(format);
        XMLOutputter xmlOut = new XMLOutputter();
        return xmlOut.outputString(top);
    }
}
