/**
 * Copyright (c) 2009--2012 Red Hat, Inc.
 *
 * This software is licensed to you under the GNU General Public License,
 * version 2 (GPLv2). There is NO WARRANTY for this software, express or
 * implied, including the implied warranties of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
 * along with this software; if not, see
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
 *
 * Red Hat trademarks are not licensed under GPLv2. No permission is
 * granted to use or replicate Red Hat trademarks that are incorporated
 * in this software or its documentation.
 */
package com.redhat.rhn.frontend.action.kickstart;

import com.redhat.rhn.common.conf.Config;
import com.redhat.rhn.common.conf.ConfigDefaults;
import com.redhat.rhn.common.db.datasource.DataResult;
import com.redhat.rhn.common.localization.LocalizationService;
import com.redhat.rhn.common.util.DatePicker;
import com.redhat.rhn.common.validator.ValidatorError;
import com.redhat.rhn.domain.action.ActionFactory;
import com.redhat.rhn.domain.kickstart.KickstartCommand;
import com.redhat.rhn.domain.kickstart.KickstartData;
import com.redhat.rhn.domain.kickstart.KickstartFactory;
import com.redhat.rhn.domain.rhnpackage.profile.Profile;
import com.redhat.rhn.domain.server.NetworkInterface;
import com.redhat.rhn.domain.server.Server;
import com.redhat.rhn.domain.server.VirtualInstanceFactory;
import com.redhat.rhn.domain.user.User;
import com.redhat.rhn.frontend.action.systems.sdc.SdcHelper;
import com.redhat.rhn.frontend.dto.OrgProxyServer;
import com.redhat.rhn.frontend.struts.RequestContext;
import com.redhat.rhn.frontend.struts.RhnValidationHelper;
import com.redhat.rhn.frontend.struts.wizard.RhnWizardAction;
import com.redhat.rhn.frontend.struts.wizard.WizardStep;
import com.redhat.rhn.frontend.taglibs.list.ListTagHelper;
import com.redhat.rhn.frontend.taglibs.list.helper.ListHelper;
import com.redhat.rhn.frontend.taglibs.list.helper.Listable;
import com.redhat.rhn.manager.kickstart.KickstartScheduleCommand;
import com.redhat.rhn.manager.kickstart.cobbler.CobblerSystemCreateCommand;
import com.redhat.rhn.manager.kickstart.cobbler.CobblerXMLRPCHelper;
import com.redhat.rhn.manager.system.SystemManager;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.ActionMessage;
import org.apache.struts.action.ActionMessages;
import org.apache.struts.action.DynaActionForm;
import org.apache.struts.util.LabelValueBean;
import org.cobbler.CobblerConnection;
import org.cobbler.CobblerObject;
import org.cobbler.Distro;
import org.cobbler.SystemRecord;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * blah blah
 *
 * @version $Rev $
 */
public class ScheduleKickstartWizardAction extends RhnWizardAction {

    /**
     * Logger for this class
     */
    private static Logger log = Logger
            .getLogger(ScheduleKickstartWizardAction.class);

    public static final String SYNCH_PACKAGES = "syncPackages";
    public static final String SYNCH_SYSTEMS = "syncSystems";
    public static final String HAS_PROFILES = "hasProfiles";
    public static final String HAS_PROXIES = "hasProxies";
    public static final String SYNC_PACKAGE_DISABED = "syncPackageDisabled";
    public static final String SYNC_SYSTEM_DISABLED = "syncSystemDisabled";
    public static final String PROXIES = "proxies";
    public static final String CNAMES = "cnames";
    public static final String VALID_CNAMES = "valid_cnames_";
    public static final String KERNEL_PARAMS = "kernelParams";
    public static final String KERNEL_PARAMS_TYPE = "kernelParamsType";
    public static final String KERNEL_PARAMS_DISTRO = "distro";
    public static final String KERNEL_PARAMS_PROFILE = "profile";
    public static final String KERNEL_PARAMS_CUSTOM = "custom";
    private static final String COBBLER_ONLY_PROFILE = "cobblerOnlyProfile";
    public static final String POST_KERNEL_PARAMS = "postKernelParams";
    public static final String POST_KERNEL_PARAMS_TYPE = "postKernelParamsType";
    public static final String PROXY_HOST = "proxyHost";
    public static final String PROXY_HOST_CNAME = "proxyHostCname";
    public static final String IS_VIRTUAL_GUEST = "isVirtualGuest";
    public static final String HOST_SID = "hostSid";
    public static final String VIRT_HOST_IS_REGISTERED = "virtHostIsRegistered";
    public static final String TARGET_PROFILE_TYPE = "targetProfileType";
    public static final String NETWORK_TYPE = "networkType";
    public static final String NETWORK_INTERFACE = "networkInterface";
    public static final String NETWORK_INTERFACES = "networkInterfaces";
    public static final String ALL_NETWORK_INTERFACES = "allNetworkInterfaces";
    public static final String USE_IPV6_GATEWAY = "useIpv6Gateway";
    public static final String BOND_TYPE = "bondType";
    public static final String BOND_INTERFACE = "bondInterface";
    public static final String BOND_SLAVE_INTERFACES = "bondSlaveInterfaces";
    public static final String HIDDEN_BOND_SLAVE_INTERFACES = "hiddenBondSlaveInterfaces";
    public static final String BOND_STATIC = "bondStatic";
    public static final String BOND_IP_ADDRESS = "bondAddress";
    public static final String BOND_NETMASK = "bondNetmask";
    public static final String BOND_GATEWAY = "bondGateway";
    public static final String BOND_OPTIONS = "bondOptions";
    public static final String CREATE_BOND_VALUE = "bonding";
    public static final String STATIC_BOND_VALUE = "true";
    public static final String DONT_CREATE_BOND_VALUE = "none";
    public static final String DESTROY_DISKS = "destroyDisks";
    public static final String NEXT_ACTION = "wizardStep";
    /**
     * {@inheritDoc}
     */
    @Override
    protected void generateWizardSteps(Map wizardSteps) {
        List methods = findMethods("run");
        for (Iterator iter = methods.iterator(); iter.hasNext();) {
            Method m = (Method) iter.next();
            String stepName = m.getName().substring(3).toLowerCase();
            WizardStep wizStep = new WizardStep();
            wizStep.setWizardMethod(m);
            log.debug("Step name: " + stepName);
            if (stepName.equals("first")) {
                wizStep.setNext("second");
                wizardSteps.put(RhnWizardAction.STEP_START, wizStep);
            }
            else if (stepName.equals("second")) {
                wizStep.setPrevious("first");
                wizStep.setNext("third");
            }
            else if (stepName.equals("third")) {
                wizStep.setPrevious("second");
            }
            else if (stepName.equals("fourth")) {
                wizStep.setPrevious("first");
            }
            else if (stepName.equals("fifth")) {
                wizStep.setPrevious("first");
            }
            wizardSteps.put(stepName, wizStep);
        }
    }

    private class Profiles implements Listable {

        /**
         * {@inheritDoc}
         */
        public List getResult(RequestContext ctx) {
            Long sid = ctx.getParamAsLong(RequestContext.SID);
            User user = ctx.getCurrentUser();

            KickstartScheduleCommand cmd = getKickstartScheduleCommand(sid,
                    user);
            DataResult profiles = cmd.getKickstartProfiles();
            if (profiles.size() == 0) {
                addMessage(ctx.getRequest(), "kickstart.schedule.noprofiles");
                ctx.getRequest().setAttribute(HAS_PROFILES,
                        Boolean.FALSE.toString());
            }
            else {
                ctx.getRequest().setAttribute(HAS_PROFILES,
                        Boolean.TRUE.toString());
            }
            return profiles;
        }
    }


    /**
     * Sets up the proxy information for the wizard.
     * its public in this class because we reuse this in SSM
     * and only this class knows how to format the name nicely.
     * @param ctx the request context needed for user info and
     *                   things to bind to the request
     */
    public static void setupProxyInfo(RequestContext ctx) {
        List<OrgProxyServer> proxies = SystemManager.
                listProxies(ctx.getCurrentUser().getOrg());
        if (proxies != null && proxies.size() > 0) {
            List<LabelValueBean> formatted = new LinkedList<LabelValueBean>();

            formatted.add(lvl10n("kickstart.schedule.default.proxy.jsp", ""));
            Map cnames = new HashMap();
            for (OrgProxyServer serv : proxies) {
                formatted.add(lv(serv.getName() + " (" + serv.getCheckin() + ")",
                        serv.getId().toString()));
                List proxyCnames = Config.get().getList(VALID_CNAMES +
                        serv.getId().toString());
                if (!proxyCnames.isEmpty()) {
                    cnames.put(serv.getId().toString(), proxyCnames);
                }
            }
            ctx.getRequest().setAttribute(HAS_PROXIES, Boolean.TRUE.toString());
            ctx.getRequest().setAttribute(PROXIES, formatted);
            ctx.getRequest().setAttribute(CNAMES, cnames);
        }
        else {
            ctx.getRequest().setAttribute(HAS_PROXIES, Boolean.FALSE.toString());
        }
    }

    private void setupBondInfo(DynaActionForm form, RequestContext context,
            KickstartScheduleCommand cmd) {
        Server server = cmd.getServer();
        List<NetworkInterface> nics = new LinkedList<NetworkInterface>
        (server.getNetworkInterfaces());

        if (nics.isEmpty()) {
            return;
        }

        for (Iterator<NetworkInterface> itr = nics.iterator(); itr.hasNext();) {
            NetworkInterface nic = itr.next();
            if ("127.0.0.1".equals(nic.getIpaddr())) {
                itr.remove();
            }
        }

        context.getRequest().setAttribute(ALL_NETWORK_INTERFACES, nics);

        if (StringUtils.isBlank(form.getString(BOND_TYPE))) {
            form.set(BOND_TYPE, DONT_CREATE_BOND_VALUE);
        }

        NetworkInterface oldBond = null;
        for (NetworkInterface nic : nics) {
            if (nic.isBond()) {
                oldBond = nic;
                break;
            }
        }

        if (oldBond != null) {
            if (StringUtils.isBlank(form.getString(BOND_INTERFACE))) {
                form.set(BOND_INTERFACE, oldBond.getName());
            }

            if (StringUtils.isBlank(form.getString(BOND_IP_ADDRESS))) {
                form.set(BOND_IP_ADDRESS, oldBond.getIpaddr());
            }

            if (StringUtils.isBlank(form.getString(BOND_NETMASK))) {
                form.set(BOND_NETMASK, oldBond.getNetmask());
            }
        }

        String[] slaves = (String[]) form.get(BOND_SLAVE_INTERFACES);
        if (slaves == null || slaves.length == 0) {
            List<String> slavesList = new ArrayList<String>();
            // if there is a bonded interface on the system
            if (!StringUtils.isBlank(form.getString(BOND_INTERFACE))) {
                for (NetworkInterface nic : nics) {
                    // if the nic does not have an IP address it is probably a
                    // slave to the bond, add it to the default selected list
                    if (StringUtils.isBlank(nic.getIpaddr())) {
                        slavesList.add(nic.getName());
                    }
                }
            }

            form.set(BOND_SLAVE_INTERFACES,
                    convertToStringArray(slavesList.toArray()));
        }
    }

    /*
     * Throwing an error when casting from Object[] to String [], so let's do it
     * manually
     */
    private String[] convertToStringArray(Object[] objects) {
        String[] strings = new String[objects.length];
        int i = 0;
        for (Object object : objects) {
            strings[i] = (String) object;
            i++;
        }
        return strings;
    }

    private List<NetworkInterface> getPublicNetworkInterfaces(
            Server server) {
        List<NetworkInterface> nics = new LinkedList<NetworkInterface>(
                server.getNetworkInterfaces());

        for (Iterator<NetworkInterface> itr = nics.iterator(); itr.hasNext();) {
            NetworkInterface nic = itr.next();
            if (nic.isDisabled() || "127.0.0.1".equals(nic.getIpaddr())) {
                itr.remove();
            }
        }

        return nics;
    }

    private void setupNetworkInfo(DynaActionForm form, RequestContext context,
            KickstartScheduleCommand cmd) {
        Server server = cmd.getServer();
        List<NetworkInterface> nics = getPublicNetworkInterfaces(server);

        if (nics.isEmpty()) {
            return;
        }

        context.getRequest().setAttribute(NETWORK_INTERFACES, nics);

        if (StringUtils.isBlank(form.getString(NETWORK_INTERFACE))) {
            String defaultInterface = ConfigDefaults.get().
                    getDefaultKickstartNetworkInterface();
            for (NetworkInterface nic : nics) {
                if (nic.getName().equals(defaultInterface)) {
                    form.set(NETWORK_INTERFACE, ConfigDefaults.get().
                            getDefaultKickstartNetworkInterface());
                }
            }
            if (StringUtils.isBlank(form.getString(NETWORK_INTERFACE))) {
                form.set(NETWORK_INTERFACE, server.
                        findPrimaryNetworkInterface().getName());
            }
        }
    }

    /**
     * The first step in the wizard
     * @param mapping ActionMapping for struts
     * @param form DynaActionForm representing the form
     * @param ctx RequestContext request context
     * @param response HttpServletResponse response object
     * @param step WizardStep what step are we on?
     *
     * @return ActionForward struts action forward
     * @throws Exception if something goes amiss
     */
    public ActionForward runFirst(ActionMapping mapping, DynaActionForm form,
            RequestContext ctx, HttpServletResponse response, WizardStep step)
                    throws Exception {
        log.debug("runFirst");
        Long sid = (Long) form.get(RequestContext.SID);
        User user = ctx.getCurrentUser();

        KickstartScheduleCommand cmd = getKickstartScheduleCommand(sid, user);

        Server system = SystemManager.lookupByIdAndUser(sid, user);
        if (system.isVirtualGuest() &&
                VirtualInstanceFactory.getInstance().getParaVirtType().equals(
                        system.getVirtualInstance().getType())) {
            ctx.getRequest().setAttribute(IS_VIRTUAL_GUEST,
                    Boolean.TRUE.toString());

            ctx.getRequest().setAttribute(VIRT_HOST_IS_REGISTERED,
                    Boolean.FALSE.toString());
            if (system.getVirtualInstance().getHostSystem() != null) {
                Long hostSid = system.getVirtualInstance().getHostSystem()
                        .getId();
                ctx.getRequest().setAttribute(VIRT_HOST_IS_REGISTERED,
                        Boolean.TRUE.toString());
                ctx.getRequest().setAttribute(HOST_SID, hostSid);
            }
        }
        else {
            ctx.getRequest().setAttribute(IS_VIRTUAL_GUEST,
                    Boolean.FALSE.toString());
        }

        addRequestAttributes(ctx, cmd, form);
        checkForKickstart(form, cmd, ctx);
        setupProxyInfo(ctx);
        if (StringUtils.isBlank(form.getString(PROXY_HOST))) {
            form.set(PROXY_HOST, "");
        }
        // create and prepopulate the date picker.
        getStrutsDelegate().prepopulateDatePicker(
                ctx.getRequest(), form, "date", DatePicker.YEAR_RANGE_POSITIVE);

        SdcHelper.ssmCheck(ctx.getRequest(), system.getId(), user);
        Map params = new HashMap<String, String>();
        params.put(RequestContext.SID, sid);
        ListHelper helper = new ListHelper(new Profiles(), ctx.getRequest(),
                params);
        helper.execute();
        if (!StringUtils.isBlank(form.getString(RequestContext.COBBLER_ID))) {
            ListTagHelper.selectRadioValue(ListHelper.LIST,
                    form.getString(RequestContext.COBBLER_ID), ctx.getRequest());
        }
        else if (system.getCobblerId() != null) {
            //if nothing is selected by the user yet, use the cobbler
            //  system record to pre-select something.
            SystemRecord rec = SystemRecord.lookupById(
                    CobblerXMLRPCHelper.getConnection(
                            ConfigDefaults.get().getCobblerAutomatedUser()),
                            system.getCobblerId());
            if (rec != null) {
                ListTagHelper.selectRadioValue(ListHelper.LIST,
                        rec.getProfile().getId(), ctx.getRequest());
            }
        }

        ActionForward retval = mapping.findForward("first");
        return retval;
    }

    /**
     * The second step in the wizard
     * @param mapping ActionMapping for struts
     * @param form DynaActionForm representing the form
     * @param ctx RequestContext request context
     * @param response HttpServletResponse response object
     * @param step WizardStep what step are we on?
     *
     * @return ActionForward struts action forward
     * @throws Exception if something goes amiss
     */
    public ActionForward runSecond(ActionMapping mapping, DynaActionForm form,
            RequestContext ctx, HttpServletResponse response, WizardStep step)
                    throws Exception {
        log.debug("runSecond");

        if (!StringUtils.isBlank(form.getString(HIDDEN_BOND_SLAVE_INTERFACES))) {
            form.set(BOND_SLAVE_INTERFACES,
                    form.getString(HIDDEN_BOND_SLAVE_INTERFACES).split(","));
        }

        Long sid = (Long) form.get(RequestContext.SID);
        User user = ctx.getCurrentUser();

        if (!validateFirstSelections(form, ctx)) {
            return runFirst(mapping, form, ctx, response, step);
        }
        KickstartScheduleCommand cmd = getScheduleCommand(form, ctx, null, null);

        checkForKickstart(form, cmd, ctx);
        addRequestAttributes(ctx, cmd, form);
        if (!cmd.isCobblerOnly()) {
            List packageProfiles = cmd.getProfiles();
            form.set(SYNCH_PACKAGES, packageProfiles);
            List systemProfiles = cmd.getCompatibleSystems();
            form.set(SYNCH_SYSTEMS, systemProfiles);

            // Disable the package/system sync radio buttons if no profiles are
            // available:
            String syncPackageDisabled = "false";
            if (packageProfiles.size() == 0) {
                syncPackageDisabled = "true";
            }
            String syncSystemDisabled = "false";
            if (systemProfiles.size() == 0) {
                syncSystemDisabled = "true";
            }
            ctx.getRequest()
            .setAttribute(SYNC_PACKAGE_DISABED, syncPackageDisabled);
            ctx.getRequest().setAttribute(SYNC_SYSTEM_DISABLED, syncSystemDisabled);

            if (StringUtils.isEmpty(form.getString(TARGET_PROFILE_TYPE))) {
                form.set(TARGET_PROFILE_TYPE,
                        KickstartScheduleCommand.TARGET_PROFILE_TYPE_NONE);
            }
        }
        else {
            ctx.getRequest().setAttribute(COBBLER_ONLY_PROFILE, Boolean.TRUE);
        }

        if (StringUtils.isEmpty(form.getString(KERNEL_PARAMS_TYPE))) {
            form.set(KERNEL_PARAMS_TYPE, KERNEL_PARAMS_DISTRO);
        }

        if (StringUtils.isEmpty(form.getString(POST_KERNEL_PARAMS_TYPE))) {
            form.set(POST_KERNEL_PARAMS_TYPE, KERNEL_PARAMS_DISTRO);
        }

        SdcHelper.ssmCheck(ctx.getRequest(), sid, user);
        return mapping.findForward("second");
    }

    protected void addRequestAttributes(RequestContext ctx,
            KickstartScheduleCommand cmd, DynaActionForm form) {
        ctx.getRequest().setAttribute(RequestContext.SYSTEM, cmd.getServer());
        ctx.getRequest()
        .setAttribute(RequestContext.KICKSTART, cmd.getKsdata());
        if (cmd.getKsdata() != null) {
            ctx.getRequest().setAttribute("profile", cmd.getKsdata());
            ctx.getRequest().setAttribute("distro", cmd.getKsdata().getTree());
            CobblerConnection con = CobblerXMLRPCHelper.
                    getConnection(ctx.getCurrentUser());

            Distro distro = Distro.lookupById(con,
                    cmd.getKsdata().getTree().getCobblerId());

            ctx.getRequest().setAttribute("distro_kernel_params",
                    distro.getKernelOptionsString());
            ctx.getRequest().setAttribute("distro_post_kernel_params",
                    distro.getKernelPostOptionsString());

            org.cobbler.Profile profile = org.cobbler.Profile.
                    lookupById(con, cmd.getKsdata().getCobblerId());
            ctx.getRequest().setAttribute("profile_kernel_params",
                    profile.getKernelOptionsString());
            ctx.getRequest().setAttribute("profile_post_kernel_params",
                    profile.getKernelPostOptionsString());
            if (cmd.getServer().getCobblerId() != null) {
                SystemRecord rec = SystemRecord.
                        lookupById(con, cmd.getServer().getCobblerId());
                if (rec != null && profile.getName().equals(rec.getProfile().getName())) {
                    if (StringUtils.isBlank(form.getString(KERNEL_PARAMS_TYPE))) {
                        form.set(KERNEL_PARAMS_TYPE, KERNEL_PARAMS_CUSTOM);
                        form.set(KERNEL_PARAMS, rec.getKernelOptionsString());
                    }
                    if (StringUtils.isBlank(form.getString(POST_KERNEL_PARAMS_TYPE))) {
                        form.set(POST_KERNEL_PARAMS_TYPE, KERNEL_PARAMS_CUSTOM);
                        form.set(POST_KERNEL_PARAMS, rec.getKernelPostOptionsString());
                    }
                }
            }
        }
        setupNetworkInfo(form, ctx, cmd);
        setupBondInfo(form, ctx, cmd);
    }



    /**
     * The third step in the wizard
     * @param mapping ActionMapping for struts
     * @param form DynaActionForm representing the form
     * @param ctx RequestContext request context
     * @param response HttpServletResponse response object
     * @param step WizardStep what step are we on?
     *
     * @return ActionForward struts action forward
     * @throws Exception if something goes amiss
     */
    public ActionForward runThird(ActionMapping mapping, DynaActionForm form,
            RequestContext ctx, HttpServletResponse response, WizardStep step)
                    throws Exception {
        log.debug("runThird");
        if (!validateBondSelections(form, ctx)) {
            return runSecond(mapping, form, ctx, response, step);
        }
        if (!validateFirstSelections(form, ctx)) {
            return runFirst(mapping, form, ctx, response, step);
        }
        String scheduleAsap = form.getString("scheduleAsap");
        Date scheduleTime = null;
        if (scheduleAsap != null && scheduleAsap.equals("false")) {
            scheduleTime = getStrutsDelegate().readDatePicker(form, "date",
                    DatePicker.YEAR_RANGE_POSITIVE);
        }
        else {
            scheduleTime = new Date();
        }
        KickstartHelper helper = new KickstartHelper(ctx.getRequest());
        KickstartScheduleCommand cmd = getScheduleCommand(form, ctx,
                scheduleTime, helper.getKickstartHost());

        if (showDiskWarning(cmd.getKsdata(), form)) {
                form.set(NEXT_ACTION, "third");
            addRequestAttributes(ctx, cmd, form);
                return mapping.findForward("fifth");
        }
        cmd.setNetworkDevice(form.getString(NETWORK_TYPE),
                form.getString(NETWORK_INTERFACE));

        if (CREATE_BOND_VALUE.equals(form.getString(BOND_TYPE))) {
            cmd.setCreateBond(true);
            cmd.setBondInterface(form.getString(BOND_INTERFACE));
            cmd.setBondOptions(form.getString(BOND_OPTIONS));
            String[] slaves = (String[]) form.get(BOND_SLAVE_INTERFACES);
            List<String> tmp = new ArrayList<String>();
            for (String slave : slaves) {
                tmp.add(slave);
            }
            cmd.setBondSlaveInterfaces(tmp);
            if (STATIC_BOND_VALUE.equals(form.getString(BOND_STATIC))) {
                cmd.setBondDhcp(false);
                cmd.setBondAddress(form.getString(BOND_IP_ADDRESS));
                cmd.setBondNetmask(form.getString(BOND_NETMASK));
                cmd.setBondGateway(form.getString(BOND_GATEWAY));
            }
            else {
                cmd.setBondDhcp(true);
            }
        }

        if (form.getString(USE_IPV6_GATEWAY).equals("1")) {
            cmd.setIpv6Gateway();
        }
        cmd.setKernelOptions(parseKernelOptions(form, ctx.getRequest(),
                form.getString(RequestContext.COBBLER_ID), false));
        cmd.setPostKernelOptions(parseKernelOptions(form, ctx.getRequest(),
                form.getString(RequestContext.COBBLER_ID), true));

        if (!cmd.isCobblerOnly()) {
            // now setup system/package profiles for kickstart to sync
            Profile pkgProfile = cmd.getKsdata().getKickstartDefaults()
                    .getProfile();
            Long packageProfileId = pkgProfile != null ? pkgProfile.getId() : null;

            // if user did not override package profile, then grab from ks
            // profile if avail
            if (packageProfileId != null) {
                cmd.setProfileId(packageProfileId);
                cmd.setProfileType(KickstartScheduleCommand.TARGET_PROFILE_TYPE_PACKAGE);
            }
            else {
                /*
                 * NOTE: these values are essentially ignored if user did not go
                 * through advanced config and there is no package profile to
                 * sync in the kickstart profile
                 */
                cmd.setProfileType(form.getString(TARGET_PROFILE_TYPE));
                cmd.setServerProfileId((Long) form.get("targetServerProfile"));
                cmd.setProfileId((Long) form.get("targetProfile"));
            }
        }

        storeProxyInfo(form, ctx, cmd);

        // Store the new KickstartSession to the DB.
        ValidatorError ve = cmd.store();
        if (ve != null) {
            ActionErrors errors = RhnValidationHelper
                    .validatorErrorToActionErrors(ve);
            if (!errors.isEmpty()) {
                getStrutsDelegate().saveMessages(ctx.getRequest(), errors);
                return runFirst(mapping, form, ctx, response, step);
            }
        }
        Map params = new HashMap();
        params.put(RequestContext.SID, form.get(RequestContext.SID));

        if (cmd.isCobblerOnly()) {
            createSuccessMessage(ctx.getRequest(),
                    "kickstart.cobbler.schedule.success", LocalizationService
                    .getInstance().formatDate(scheduleTime));
            return getStrutsDelegate().forwardParams(
                    mapping.findForward("cobbler-success"), params);
        }
        createSuccessMessage(ctx.getRequest(), "kickstart.schedule.success",
                LocalizationService.getInstance().formatDate(scheduleTime));
        return getStrutsDelegate().forwardParams(
                mapping.findForward("success"), params);
    }

    /**
     * Setup the system for provisioning with cobbler.
     *
     * @param mapping ActionMapping for struts
     * @param form DynaActionForm representing the form
     * @param ctx RequestContext request context
     * @param response HttpServletResponse response object
     * @param step WizardStep what step are we on?
     *
     * @return ActionForward struts action forward
     * @throws Exception if something goes amiss
     */
    public ActionForward runFourth(ActionMapping mapping, DynaActionForm form,
            RequestContext ctx, HttpServletResponse response, WizardStep step)
                    throws Exception {

        log.debug("runFourth");
        if (!validateFirstSelections(form, ctx)) {
            return runFirst(mapping, form, ctx, response, step);
        }
        Long sid = (Long) form.get(RequestContext.SID);
        String cobblerId = form.getString(RequestContext.COBBLER_ID);

        log.debug("runFourth.cobblerId: " + cobblerId);

        User user = ctx.getCurrentUser();
        Server server = SystemManager.lookupByIdAndUser(sid, user);

        Map params = new HashMap();
        params.put(RequestContext.SID, sid);

        log.debug("Creating cobbler system record");
        org.cobbler.Profile profile = org.cobbler.Profile.lookupById(
                CobblerXMLRPCHelper.getConnection(user), cobblerId);

        KickstartData data = KickstartFactory.lookupKickstartDataByCobblerIdAndOrg(
                user.getOrg(), profile.getUid());

        if (showDiskWarning(data, form)) {
                form.set(NEXT_ACTION, "fourth");
                return mapping.findForward("fifth");
        }

        CobblerSystemCreateCommand cmd = new CobblerSystemCreateCommand(server,
                profile.getName(), data);
        cmd.store();
        log.debug("cobbler system record created.");
        String[] args = new String[2];
        args[0] = server.getName();
        args[1] = profile.getName();
        createMessage(ctx.getRequest(), "kickstart.schedule.cobblercreate",
                args);
        return getStrutsDelegate().forwardParams(
                mapping.findForward("cobbler-success"), params);
    }

    /**
     * Returns the kickstart schedule command
     * @param form the dyna aciton form
     * @param ctx the request context
     * @param scheduleTime the schedule time
     * @param host the host url.
     * @return the Ks schedule command
     */
    protected KickstartScheduleCommand getScheduleCommand(DynaActionForm form,
            RequestContext ctx, Date scheduleTime, String host) {
        String cobblerId = form.getString(RequestContext.COBBLER_ID);
        User user = ctx.getCurrentUser();
        KickstartScheduleCommand cmd;
        KickstartData data = KickstartFactory
                .lookupKickstartDataByCobblerIdAndOrg(user.getOrg(), cobblerId);
        if (data != null) {
            cmd = new KickstartScheduleCommand((Long) form
                    .get(RequestContext.SID), data, ctx.getCurrentUser(),
                    scheduleTime, host);
        }
        else {
            org.cobbler.Profile profile = org.cobbler.Profile.lookupById(
                    CobblerXMLRPCHelper.getConnection(user), cobblerId);
            cmd = KickstartScheduleCommand.createCobblerScheduleCommand(
                    (Long) form.get(RequestContext.SID), profile.getName(),
                    user, scheduleTime, host);
        }
        return cmd;
    }

    /**
     * @param form the form containing the proxy info
     * @param ctx the request context associated to this request
     * @param cmd the kicktstart command to which the proxy info will be
     * copied..
     */
    protected void storeProxyInfo(DynaActionForm form, RequestContext ctx,
            KickstartScheduleCommand cmd) {
        // if we need to go through a proxy, do it here.
        String phost = form.getString(PROXY_HOST);
        String phostCname = form.getString(PROXY_HOST_CNAME);

        if (!StringUtils.isEmpty(phostCname)) {
            cmd.setProxyHost(phostCname);
        }
        else if (!StringUtils.isEmpty(phost)) {
            cmd.setProxy(SystemManager.lookupByIdAndOrg(new Long(phost), ctx
                    .getCurrentUser().getOrg()));
        }
    }

    protected boolean validateBondSelections(DynaActionForm form,
            RequestContext ctx) {
        if (!StringUtils.isBlank(form.getString(HIDDEN_BOND_SLAVE_INTERFACES))) {
            form.set(BOND_SLAVE_INTERFACES,
                    form.getString(HIDDEN_BOND_SLAVE_INTERFACES).split(","));
        }

        String[] slaves = (String[]) form.get(BOND_SLAVE_INTERFACES);
        ActionErrors errors = new ActionErrors();

        // if we are trying to create a bond but have not specified a name or at
        // least one slave interface
        if (form.getString(BOND_TYPE).equals(CREATE_BOND_VALUE) &&
                (StringUtils.isBlank(form.getString(BOND_INTERFACE)) ||
                        slaves.length < 1 || StringUtils.isBlank(slaves[0]))) {
            errors.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage(
                    "kickstart.bond.not.defined.jsp"));
        }

        final String ipv4addressPattern = "^([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." +
                "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." +
                "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." +
                "([01]?\\d\\d?|2[0-4]\\d|25[0-5])$";

        /*
         * The IPv6 address regex was created by Stephen Ryan at Dartware
         * and taken from this forum: http://forums.intermapper.com/viewtopic.php?t=452
         * It is licenced under a Creative Commons Attribution-ShareAlike 3.0 Unported
         * License. We can freely use it in (even in commercial products) as long as
         * we attribute its creation to him, so don't remove this message.
         */
        final String ipv6addressPattern = "^(((?=(?>.*?::)(?!.*::)))(::)?([0-9A-" +
                "F]{1,4}::?){0,5}|([0-9A-F]{1,4}:){6})(\\2([0-9A-F]{1,4}(::?|$))" +
                "{0,2}|((25[0-5]|(2[0-4]|1\\d|[1-9])?\\d)(\\.|$)){4}|[0-9A-F]{1," +
                "4}:[0-9A-F]{1,4})(?<![^:]:|\\.)\\z";

        if (form.getString(BOND_STATIC).equals(STATIC_BOND_VALUE)) {
            String address = form.getString(BOND_IP_ADDRESS);
            String netmask = form.getString(BOND_NETMASK);
            String gateway = form.getString(BOND_GATEWAY);

            if (!address.matches(ipv4addressPattern) &&
                    !address.matches(ipv6addressPattern)) {
                errors.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage(
                        "kickstart.bond.bad.ip.address.jsp"));
            }

            if (!netmask.matches(ipv4addressPattern) &&
                    !netmask.matches(ipv6addressPattern)) {
                errors.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage(
                        "kickstart.bond.bad.netmask.jsp"));
            }

            if (!gateway.matches(ipv4addressPattern) &&
                    !gateway.matches(ipv6addressPattern)) {
                errors.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage(
                        "kickstart.bond.bad.ip.address.jsp"));
            }
        }

        if (errors.size() > 0) {
            addErrors(ctx.getRequest(), errors);
            return false;
        }
        return true;
    }

    protected boolean validateFirstSelections(DynaActionForm form,
            RequestContext ctx) {
        String cobblerId = ListTagHelper.getRadioSelection(ListHelper.LIST,
                ctx.getRequest());
        if (StringUtils.isBlank(cobblerId)) {
            cobblerId = ctx.getParam(RequestContext.COBBLER_ID, true);
        }

        boolean retval = false;
        form.set(RequestContext.COBBLER_ID, cobblerId);
        ctx.getRequest().setAttribute(RequestContext.COBBLER_ID, cobblerId);
        if (form.get("scheduleAsap") != null) {
            retval = true;
        }
        else if (form.get(RequestContext.COBBLER_ID) != null) {
            return true;
        }
        return retval;
    }

    /*
     * If the user is going to clear all partitions from all drives we need to ask
     * him if he's sure first.
     */
    protected boolean showDiskWarning(KickstartData data, DynaActionForm form) {
        Set<KickstartCommand> commands = data.getOptions();
        boolean containsClearpartCommand = false;
        for (KickstartCommand command : commands) {
            if (command.getCommandName() != null &&
                command.getCommandName().getName() != null &&
                command.getCommandName().getName().equals("clearpart")) {
                if (command.getArguments() != null &&
                    command.getArguments().contains("--drives")) {
                    return false;
                }
                    containsClearpartCommand = true;
                    break;
                }
                continue;
        }

        String diskOption = form.getString(DESTROY_DISKS);
        if (!containsClearpartCommand || (diskOption != null &&
            diskOption.equals("true"))) {
             return false;
        }
        return true;
    }

    private void checkForKickstart(DynaActionForm form,
            KickstartScheduleCommand cmd, RequestContext ctx) {
        if (ActionFactory.doesServerHaveKickstartScheduled((Long) form
                .get(RequestContext.SID))) {
            String[] params = { cmd.getServer().getName() };
            getStrutsDelegate().saveMessage(
                    "kickstart.schedule.already.scheduled.jsp", params,
                    ctx.getRequest());
        }
    }

    protected KickstartScheduleCommand getKickstartScheduleCommand(Long sid,
            User currentUser) {
        return new KickstartScheduleCommand(sid, currentUser);
    }

    /**
     * Parses the kernel options or Post kernel options
     * from the given form. Called after the advanced options page
     * is typically set..
     *  This is a handy method used in both SSM and SDC KS scheduling.
     * @param form the kickstartScheduleWizardForm that holds the form fields.
     * @param request the servlet request
     * @param profileCobblerId the cobbler profile id
     * @param isPost true if caller is interested in getting the
     *              post kernel options and not the pre.
     * @return the kernel options selected by the user.
     */
    public static String parseKernelOptions(DynaActionForm form,
            HttpServletRequest request,
            String profileCobblerId,
            boolean isPost) {
        RequestContext context = new RequestContext(request);
        String typeKey = !isPost ? KERNEL_PARAMS_TYPE : POST_KERNEL_PARAMS_TYPE;
        String customKey = !isPost ? KERNEL_PARAMS : POST_KERNEL_PARAMS;
        String type = form.getString(typeKey);

        return parseKernelOptions(form.getString(customKey), type, profileCobblerId,
                isPost, context.getCurrentUser());
    }


    /**
     * Parses the kernel options or Post kernel options
     * from the given set of params
     *  This is a handy method used in both SSM and SDC KS scheduling.
     * @param customOptions the kickstartScheduleWizardForm that holds the form fields.
     * @param paramsType  either KERNEL_PARAMS_CUSTOM _DISTRO or _PROFILE
     * @param cobblerId the cobbler profile id
     * @param isPost true if caller is interested in getting the
     *              post kernel options and not the pre.
     * @param user the user doing the request
     * @return the kernel options selected by the user.
     */
    public static String parseKernelOptions(String customOptions,
            String paramsType,
            String cobblerId,
            boolean isPost, User user) {

        CobblerConnection con  = CobblerXMLRPCHelper.
                getConnection(user);
        if (KERNEL_PARAMS_CUSTOM.equals(paramsType)) {
            return customOptions;
        }
        org.cobbler.Profile profile = org.cobbler.Profile.lookupById(con,
                cobblerId);
        CobblerObject ret = profile;

        if (KERNEL_PARAMS_DISTRO.equals(paramsType)) {
            ret = profile.getDistro();
        }
        if (!isPost) {
            return ret.getKernelOptionsString();
        }
        return ret.getKernelPostOptionsString();

    }
}
