/*
 * 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, version 2, as
 * published by the Free Software Foundation, and/or the GNU Lesser
 * General Public License, version 2.1, also as published by the Free
 * Software Foundation.
 *
 * 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 and the GNU Lesser General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License
 * and the GNU Lesser General Public License along with this program;
 * if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */
package org.rhq.plugins.platform.content;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.HashSet;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.rhq.core.domain.content.PackageDetailsKey;
import org.rhq.core.domain.content.PackageType;
import org.rhq.core.domain.content.transfer.ResourcePackageDetails;
import org.rhq.core.system.ProcessExecution;
import org.rhq.core.system.ProcessExecutionResults;
import org.rhq.core.system.SystemInfo;

/**
 * Handles discovering RPMs installed on a Linux system.
 *
 * @author Jason Dobies
 */
public class RpmPackageDiscoveryDelegate {
    // Static  --------------------------------------------

    private static SystemInfo systemInfo;

    /**
     * Holds the full file name of the RPM executable if found. Will contain <code>null</code> if there is no RPM
     * executable on the system.
     */
    private static String rpmExecutable;

    // Public  --------------------------------------------

    public static void checkExecutables() {
        // Make sure rpm is actually on the system
        ProcessExecution processExecution = new ProcessExecution("/usr/bin/which");
        processExecution.setArguments(new String[] { "rpm" });
        processExecution.setCaptureOutput(true);

        ProcessExecutionResults executionResults = systemInfo.executeProcess(processExecution);
        String capturedOutput = executionResults.getCapturedOutput();

        rpmExecutable = (((capturedOutput == null) || "".equals(capturedOutput)) ? null : capturedOutput.trim());
    }

    public static void setSystemInfo(SystemInfo systemInfo) {
        RpmPackageDiscoveryDelegate.systemInfo = systemInfo;
    }

    public static Set<ResourcePackageDetails> discoverPackages(PackageType type) throws IOException {
        Log log = LogFactory.getLog(RpmPackageDiscoveryDelegate.class);

        Set<ResourcePackageDetails> packages = new HashSet<ResourcePackageDetails>();

        if (rpmExecutable == null) {
            return packages;
        }

        /* Sample output from: rpm -qa
         * xorg-x11-fonts-ethiopic-7.2-3.fc8 bitmap-fonts-0.3-5.1.2.fc7 bluez-utils-cups-3.20-4.fc8
         *
         * In short, it's a list of installed packages.
         */
        ProcessExecution listRpmsProcess = new ProcessExecution(rpmExecutable);
        listRpmsProcess.setArguments(new String[] { "-qa" });
        listRpmsProcess.setCaptureOutput(true);

        ProcessExecutionResults executionResults = systemInfo.executeProcess(listRpmsProcess);
        String capturedOutput = executionResults.getCapturedOutput();

        // Process the resulting output
        if (capturedOutput == null) {
            return packages;
        }

        BufferedReader rpmNameReader = new BufferedReader(new StringReader(capturedOutput));

        String rpmName;

        while ((rpmName = rpmNameReader.readLine()) != null) {
            String name = null;
            String version = null;
            String architectureName = null;

            try {

                // Execute RPM query for each RPM
                ProcessExecution rpmQuery = new ProcessExecution(rpmExecutable);
                rpmQuery
                    .setArguments(new String[] {
                        "-q",
                        "--qf",
                        "%{NAME}\\n%{VERSION}.%{RELEASE}\\n%{ARCH}\\n%{INSTALLTIME}\\n%{FILEMD5S}\\n%{GROUP}\\n%{FILENAMES}\\n%{SIZE}\\n%{LICENSE}\\n%{DESCRIPTION}",
                        rpmName });
                rpmQuery.setCaptureOutput(true);

                ProcessExecutionResults rpmDataResults = systemInfo.executeProcess(rpmQuery);
                String rpmData = rpmDataResults.getCapturedOutput();

                BufferedReader rpmDataReader = new BufferedReader(new StringReader(rpmData));

                name = rpmDataReader.readLine();
                version = rpmDataReader.readLine();
                architectureName = rpmDataReader.readLine();
                String installTimeString = rpmDataReader.readLine();
                String md5 = rpmDataReader.readLine();
                String category = rpmDataReader.readLine();
                String fileName = rpmDataReader.readLine();
                String sizeString = rpmDataReader.readLine();
                String license = rpmDataReader.readLine();

                StringBuffer description = new StringBuffer();
                String descriptionTemp;
                while ((descriptionTemp = rpmDataReader.readLine()) != null) {
                    description.append(descriptionTemp);
                }

                Long fileSize = null;
                if (sizeString != null) {
                    try {
                        fileSize = Long.parseLong(sizeString);
                    } catch (NumberFormatException e) {
                        log.warn("Could not parse file size: " + sizeString);
                    }
                }

                Long installTime = null;
                if (installTimeString != null) {
                    try {
                        installTime = Long.parseLong(installTimeString);
                        installTime *= 1000L; // RPM returns epoch seconds, we need epoch millis
                    } catch (NumberFormatException e) {
                        log.debug("Could not parse package install time");
                    }
                }

                // There may be multiple file names. For now, just ignore the package (I'll find a better way
                // to deal with this going forward). There will be a blank space in the fileName attribute, so
                // just abort if we see that
                if ((fileName == null) || fileName.equals("")) {
                    continue;
                }

                PackageDetailsKey key = new PackageDetailsKey(name, version, "rpm", architectureName);
                ResourcePackageDetails packageDetails = new ResourcePackageDetails(key);
                packageDetails.setClassification(category);
                packageDetails.setDisplayName(name);
                packageDetails.setFileName(fileName);
                if (!md5.startsWith("00000000000000000000000000000000")) {
                    if (md5.length() <= 32) {
                        packageDetails.setMD5(md5);
                    } else {
                        packageDetails.setSHA256(md5); // md5's can only be 32 chars, anything more and we assume its a SHA256
                    }
                }
                packageDetails.setFileSize(fileSize);
                packageDetails.setLicenseName(license);
                packageDetails.setLongDescription(description.toString());
                packageDetails.setInstallationTimestamp(installTime);
                packages.add(packageDetails);
            } catch (Exception e) {
                log.error("Error creating resource package. RPM Name: " + rpmName + " Version: " + version
                    + " Architecture: " + architectureName);
            }

        }

        return packages;

    }
}
