/**
 * Copyright (c) 2009--2015 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.configuration;

import com.redhat.rhn.common.conf.Config;
import com.redhat.rhn.common.conf.ConfigDefaults;
import com.redhat.rhn.common.util.StringUtil;
import com.redhat.rhn.common.validator.ValidatorError;
import com.redhat.rhn.common.validator.ValidatorResult;
import com.redhat.rhn.domain.config.ConfigChannel;
import com.redhat.rhn.domain.config.ConfigFile;
import com.redhat.rhn.domain.config.ConfigFileType;
import com.redhat.rhn.domain.config.ConfigRevision;
import com.redhat.rhn.domain.role.RoleFactory;
import com.redhat.rhn.domain.user.User;
import com.redhat.rhn.frontend.struts.RequestContext;
import com.redhat.rhn.frontend.struts.ScrubbingDynaActionForm;
import com.redhat.rhn.frontend.struts.StrutsDelegate;
import com.redhat.rhn.manager.configuration.ConfigurationManager;
import com.redhat.rhn.manager.configuration.ConfigurationValidation;
import com.redhat.rhn.manager.configuration.file.BinaryFileData;
import com.redhat.rhn.manager.configuration.file.ConfigFileData;
import com.redhat.rhn.manager.configuration.file.DirectoryData;
import com.redhat.rhn.manager.configuration.file.SymlinkData;
import com.redhat.rhn.manager.configuration.file.TextFileData;
import java.io.BufferedInputStream;

import org.apache.struts.upload.FormFile;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.text.DecimalFormat;

import javax.servlet.http.HttpServletRequest;

/**
 * ConfigFileForm
 * @version $Rev$
 */
public class ConfigFileForm extends ScrubbingDynaActionForm {

    /**
     * Comment for <code>serialVersionUID</code>
     */
    private static final long serialVersionUID = -2162768922109257186L;
    // configFileForm elements
    public static final String REV_PATH         = "cffPath";
    public static final String REV_SYMLINK_TARGET_PATH = "targetPath";
    public static final String REV_UID          = "cffUid";
    public static final String REV_GID          = "cffGid";
    public static final String REV_PERMS        = "cffPermissions";
    public static final String REV_SELINUX_CTX  = "cffSELinuxCtx";
    public static final String REV_MACROSTART   = "cffMacroStart";
    public static final String REV_MACROEND     = "cffMacroEnd";
    public static final String REV_CONTENTS     = "contents"; //cffContent
    public static final String REV_FILETYPE     = "filetype"; //cffFiletype
    public static final String REV_BINARY       = "binary";
    public static final String REV_UPLOAD       = "cffUpload";

    public static final String REV_EDITABLE      = "editable";
    public static final String REV_DISPLAYABLE   = "displayable";
    public static final String REV_TOOLARGE      = "toolarge";
    public static final String REV_NUMBER        = "revnum";



    public static final String DEFAULT_CONFIG_DELIM_START = "{|";
    public static final String DEFAULT_CONFIG_DELIM_END = "|}";

    // This cannot be the right way to find this
    // Here to deal with form-size-limits
    public static final long   MAX_EDITABLE_SIZE;
    static {
        MAX_EDITABLE_SIZE = new Long(ConfigDefaults.get().getConfigFileEditSize() * 1024);
    }

    /**
     * Set acceptable defaults for our form
     */
    public void setDefaults() {
        set(ConfigFileForm.REV_PERMS, "644");
        set(ConfigFileForm.REV_UID, "root");
        set(ConfigFileForm.REV_GID, "root");
        set(ConfigFileForm.REV_FILETYPE, ConfigFileType.FILE);
        setBinary(false);

        String macroStart = Config.get().getString("web.config_delim_start",
                DEFAULT_CONFIG_DELIM_START);
        String macroEnd = Config.get().getString("web.config_delim_end",
                DEFAULT_CONFIG_DELIM_END);
        set(ConfigFileForm.REV_MACROSTART, macroStart);
        set(ConfigFileForm.REV_MACROEND, macroEnd);
    }


    /**
     * Validate a file-upload. This checks that:
     * <ul>
     * <li>The file exists
     * <li>The file isn't too large
     * <li>If the file is text, its contents are valid after macro-substitution
     * </ul>
     * @param request the incoming request
     * @return a ValidatorResult..  The list is empty if everything is OK
     */
    public ValidatorResult validateUpload(HttpServletRequest request) {
        ValidatorResult msgs = new ValidatorResult();

        FormFile file = (FormFile)get(REV_UPLOAD);
        //make sure there is a file
        if (file == null ||
            file.getFileName() == null ||
            file.getFileName().trim().length() == 0) {
            msgs.addError(new ValidatorError("error.config-not-specified"));

        }
        else if (file.getFileSize() == 0) {
            msgs.addError(new ValidatorError("error.config-empty",
                                                        file.getFileName()));
        }
        //make sure they didn't send in something huge
        else if (file.getFileSize() > ConfigFile.getMaxFileSize()) {
            msgs.addError(new ValidatorError("error.configtoolarge",
                    StringUtil.displayFileSize(ConfigFile.getMaxFileSize(), false)));
        }
        // It exists and isn't too big - is it text?
        else if (!isBinary()) {
            try {
                String content = new String(file.getFileData());
                String startDelim = getString(REV_MACROSTART);
                String endDelim   = getString(REV_MACROEND);
                msgs.append(ConfigurationValidation.validateContent(
                                    content, startDelim, endDelim));
            }
            catch (Exception e) {
                msgs.addError(new ValidatorError("error.fatalupload",
                                  StringUtil.displayFileSize(
                                          ConfigFile.getMaxFileSize(), false)));
            }
        }

        return msgs;
    }

    /**
     * Given the incoming request, fill us in with revision info
     * @param request the request
     * @param cr the revision we're getting data from
     */
    public void updateFromRevision(HttpServletRequest request, ConfigRevision cr) {

        RequestContext requestContext = new RequestContext(request);

        User u = requestContext.getCurrentUser();

        set(ConfigFileForm.REV_PATH, cr.getConfigFile().getConfigFileName().getPath());
        if (cr.isSymlink() && cr.getConfigInfo().getTargetFileName() != null) {
            set(ConfigFileForm.REV_SYMLINK_TARGET_PATH,
                    cr.getConfigInfo().getTargetFileName().getPath());
        }

        set(REV_NUMBER, String.valueOf(
                cr.getConfigFile().getLatestConfigRevision().getRevision() + 1));

        Long mode = cr.getConfigInfo().getFilemode();

        String modeStr = mode == null ? "" :
            new DecimalFormat("000").format(mode.longValue());
        set(ConfigFileForm.REV_PERMS, modeStr);
        set(ConfigFileForm.REV_SELINUX_CTX, cr.getConfigInfo().getSelinuxCtx());
        set(ConfigFileForm.REV_UID, cr.getConfigInfo().getUsername());
        set(ConfigFileForm.REV_GID, cr.getConfigInfo().getGroupname());
        set("submitted", Boolean.TRUE);
        if (cr.isFile()) {
            set(ConfigFileForm.REV_BINARY, cr.getConfigContent().isBinary());
            if (!cr.getConfigContent().isBinary()) {
                set(ConfigFileForm.REV_CONTENTS, cr.getConfigContent().getContentsString());
            }
            Boolean toolarge = cr.getConfigContent().getFileSize().
                                                longValue() > MAX_EDITABLE_SIZE;
            request.setAttribute(REV_TOOLARGE, toolarge);
        }
        ConfigActionHelper.setupRequestAttributes(requestContext, cr.getConfigFile(), cr);

        request.setAttribute(REV_DISPLAYABLE, canDisplayContent(cr));
        request.setAttribute(REV_EDITABLE, canEditContent(u, cr));


    }

    /**
     * You can DISPLAY content IFF
     *   - It's not a directory
     *   - It's not binary
     *   - It's not "too damn big"
     * @param cr file of interest
     * @return true IFF the above conditions are true
     */
    protected boolean canDisplayContent(ConfigRevision cr) {
        return (cr.isFile() &&
                !cr.getConfigContent().isBinary() &&
                cr.getConfigContent().getFileSize().longValue() < MAX_EDITABLE_SIZE);
    }

    /**
     * You can edit a file IFF:
     *   - You're a config-admin, and this is a GLOBAL channel
     *   - You're a system-admin, and this is a LOCAL or SANDBOX channel
     * @param user logged-in user making the request
     * @param cr revision to be edited
     * @return true IFF user has write-access to the file
     */
    protected boolean canEditContent(User user, ConfigRevision cr) {
        ConfigurationManager mgr = ConfigurationManager.getInstance();
        ConfigChannel cc = cr.getConfigFile().getConfigChannel();

        if (cc.isGlobalChannel()) {
            return (user.hasRole(RoleFactory.CONFIG_ADMIN));
        }
        return (mgr.accessToChannel(user.getId(), cc.getId()));
    }

    private boolean isUpload() {
        return get(REV_UPLOAD) != null;
    }

    /**
     *
     * @return true if content is binary false other wise
     */
    private boolean isBinary() {
        return Boolean.TRUE.equals(get(REV_BINARY));
    }

    /**
     * Returns the config file type of the content
     * @return dir/file...
     */
    private ConfigFileType extractFileType() {
        String ft = getString(ConfigFileForm.REV_FILETYPE);
        return ConfigFileType.lookup(ft);
    }



    /**
     * sets if the file is a binary or text.
     * @param isBinary true if this file is a binary
     */
    private void setBinary(boolean isBinary) {
        set(REV_BINARY, Boolean.valueOf(isBinary));
    }

    /**
     *
     * @return true if this holds a dir, returns false if it holds a file..
     */
    private boolean isDirectory() {
        return ConfigFileType.dir().equals(extractFileType());
    }

    private String getContents() {
        return StringUtil.webToLinux(getString(ConfigFileForm.REV_CONTENTS));
    }
    /**
     *
     * @return a ConfigFileData representation of this Form
     */
    public ConfigFileData toData() {
        ConfigFileData data;
        if (isDirectory()) {
            data = new DirectoryData();
        }
        else if (ConfigFileType.symlink().equals(extractFileType())) {
            data = new SymlinkData(getString(REV_SYMLINK_TARGET_PATH));
        }
        else if (isBinary()) {
            if (isUpload()) {
                FormFile file = (FormFile) get(REV_UPLOAD);
                try {
                    data = new BinaryFileData(new BufferedInputStream(
                            file.getInputStream()), file.getFileSize());
                }
                catch (IOException e) {
                    String msg = "Unable to read the uploaded binary file stream";
                    throw new RuntimeException(msg, e);
                }
            }
            else {
                data = new BinaryFileData(new ByteArrayInputStream(new byte[0]), 0);
            }
        }
        else {
            if (isUpload()) {
                FormFile file = (FormFile) get(REV_UPLOAD);
                StrutsDelegate del = StrutsDelegate.getInstance();
                data = new TextFileData(del.extractString(file));
            }
            else {
                data = new TextFileData(getContents());
            }
            data.setMacroStart(getString(REV_MACROSTART));
            data.setMacroEnd(getString(REV_MACROEND));
        }
        data.setPath(getString(REV_PATH));
        data.setGroup(getString(REV_GID));
        data.setOwner(getString(REV_UID));
        data.setPermissions(getString(REV_PERMS));
        data.setSelinuxCtx(getString(REV_SELINUX_CTX));
        data.setRevNumber(getString(REV_NUMBER));
        data.setType(extractFileType());
        return data;
    }

    /**
     * Returns a ConfigFileData representation of this Form, similar to toData()
     * however in addition it replicates the contents of the passed in revision
     * rev to the ConfigFileData.. This is mainly used in the FileDetailAction
     * where want the contents of a "non-displayable" file replicated on to the
     * newer revision...
     * @param rev the revision to replicate the content stream.
     * @return the newly updated revision..
     */
    public ConfigFileData toRevisedData(ConfigRevision rev) {
        ConfigFileData data = toData();
        boolean toBeBinary = (Boolean)get(REV_BINARY) == null ?
                rev.isFile() && rev.getConfigContent().isBinary() :
                 isBinary();
        if (!canDisplayContent(rev) || toBeBinary) {
            data.processRevisedContentFrom(rev);
        }
        return data;

    }
}
