/*
 * Zed Attack Proxy (ZAP) and its related class files.
 *
 * ZAP is an HTTP/HTTPS proxy for assessing web application security.
 *
 * Copyright 2012 The ZAP Development Team
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.zaproxy.zap.extension.autoupdate;

import java.io.File;
import java.security.InvalidParameterException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.parosproxy.paros.Constant;
import org.parosproxy.paros.common.AbstractParam;
import org.zaproxy.zap.extension.api.ZapApiIgnore;

public class OptionsParamCheckForUpdates extends AbstractParam {

    public static final String CHECK_ON_START = "start.checkForUpdates";
    public static final String DAY_LAST_CHECKED = "start.dayLastChecked";
    public static final String DAY_LAST_INSTALL_WARNED = "start.dayLastInstallWarned";
    public static final String DAY_LAST_UPDATE_WARNED = "start.dayLastUpdateWarned";
    public static final String DOWNLOAD_NEW_RELEASE = "start.downloadNewRelease";
    public static final String CHECK_ADDON_UPDATES = "start.checkAddonUpdates";
    public static final String INSTALL_ADDON_UPDATES = "start.installAddonUpdates";
    public static final String INSTALL_SCANNER_RULES = "start.installScannerRules";
    public static final String REPORT_RELEASE_ADDON = "start.reportReleaseAddons";
    public static final String REPORT_BETA_ADDON = "start.reportBetaAddons";
    public static final String REPORT_ALPHA_ADDON = "start.reportAlphaAddons";
    public static final String ADDON_DIRS = "start.addonDirs";
    public static final String DOWNLOAD_DIR = "start.downloadDir";

    private static String SDF_FORMAT = "yyyy-MM-dd";

    private boolean checkOnStart = true;
    private boolean downloadNewRelease = false;
    private boolean checkAddonUpdates = true;
    private boolean installAddonUpdates = false;
    private boolean installScannerRules = false;
    private boolean reportReleaseAddons = false;
    private boolean reportBetaAddons = false;
    private boolean reportAlphaAddons = false;
    private List<File> addonDirectories = new ArrayList<>();
    private File downloadDirectory = new File(Constant.FOLDER_LOCAL_PLUGIN);

    // Day last checked is used to ensure if the user has agreed then we only check the first time
    // ZAP is run every day
    private String dayLastChecked = null;
    private String dayLastInstallWarned = null;
    private String dayLastUpdateWarned = null;
    private static final Logger LOGGER = LogManager.getLogger(OptionsParamCheckForUpdates.class);

    public OptionsParamCheckForUpdates() {}

    @Override
    protected void parse() {
        checkOnStart = getBoolean(CHECK_ON_START, true);
        dayLastChecked = getString(DAY_LAST_CHECKED, "");
        dayLastInstallWarned = getString(DAY_LAST_INSTALL_WARNED, "");
        dayLastUpdateWarned = getString(DAY_LAST_UPDATE_WARNED, "");

        downloadNewRelease = getBoolean(DOWNLOAD_NEW_RELEASE, false);
        checkAddonUpdates = getBoolean(CHECK_ADDON_UPDATES, true);
        installAddonUpdates = getBoolean(INSTALL_ADDON_UPDATES, false);
        installScannerRules = getBoolean(INSTALL_SCANNER_RULES, false);
        reportReleaseAddons = getBoolean(REPORT_RELEASE_ADDON, false);
        reportBetaAddons = getBoolean(REPORT_BETA_ADDON, false);
        reportAlphaAddons = getBoolean(REPORT_ALPHA_ADDON, false);
        for (Object dir : getConfig().getList(ADDON_DIRS)) {
            File f = new File(dir.toString());
            if (!f.exists()) {
                LOGGER.error("Add-on directory does not exist: {}", f.getAbsolutePath());
            } else if (!f.isDirectory()) {
                LOGGER.error("Add-on directory is not a directory: {}", f.getAbsolutePath());
            } else if (!f.canRead()) {
                LOGGER.error("Add-on directory not readable: {}", f.getAbsolutePath());
            } else {
                this.addonDirectories.add(f);
            }
        }
        setDownloadDirectory(
                new File(getString(DOWNLOAD_DIR, Constant.FOLDER_LOCAL_PLUGIN)), false);
    }

    /**
     * Sets whether or not the "check for updates on start up" is enabled.
     *
     * @param checkOnStart {@code true} if the "check for updates on start up" should be enabled,
     *     {@code false} otherwise.
     */
    public void setCheckOnStart(boolean checkOnStart) {
        this.checkOnStart = checkOnStart;
        getConfig().setProperty(CHECK_ON_START, checkOnStart);
        if (dayLastChecked.length() == 0) {
            dayLastChecked = "Never";
            getConfig().setProperty(DAY_LAST_CHECKED, dayLastChecked);
        }
    }

    /**
     * Tells whether or not the option "check for updates on start up" is enabled.
     *
     * @return {@code true} if check for updates on start up is enabled, {@code false} otherwise.
     * @see #checkOnStart()
     */
    public boolean isCheckOnStart() {
        return checkOnStart;
    }

    /**
     * Get a new SimpleDateFormat each time for thread safeness
     *
     * @return
     */
    private SimpleDateFormat getSdf() {
        return new SimpleDateFormat(SDF_FORMAT);
    }

    /**
     * Tells whether or not a "check for updates on start up" needs to be performed.
     *
     * <p>A check for updates needs to be performed if the method {@code isCheckOnStart()} returns
     * {@code true} and if no check was already done during the same day.
     *
     * @return {@code true} if a check for updates on start up needs to be performed, {@code false}
     *     otherwise.
     * @see #isCheckOnStart()
     */
    @ZapApiIgnore
    public boolean checkOnStart() {
        if (!checkOnStart) {
            LOGGER.debug("isCheckForStart - false");
            return false;
        }
        String today = getSdf().format(new Date());
        if (today.equals(dayLastChecked)) {
            LOGGER.debug("isCheckForStart - already checked today");
            return false;
        }
        getConfig().setProperty(DAY_LAST_CHECKED, today);
        try {
            getConfig().save();
        } catch (ConfigurationException e) {
            LOGGER.error(e.getMessage(), e);
        }

        return true;
    }

    /**
     * Returns the date the last check for updates check was made, or null if no check has been made
     *
     * @return
     */
    public Date getDayLastChecked() {
        try {
            return getSdf().parse(dayLastChecked);
        } catch (ParseException e) {
            // Assume its not been checked
            return null;
        }
    }

    /**
     * Returns the date the last check for warning about out of date ZAP / add-ons was made, or null
     * if no check has been made
     *
     * @return
     */
    public Date getDayLastInstallWarned() {
        try {
            return getSdf().parse(dayLastInstallWarned);
        } catch (ParseException e) {
            // Assume we've never warned
            return null;
        }
    }

    /**
     * Returns the date the last check for warning about out of date add-ons was made, or null if no
     * check has been made
     *
     * @return
     */
    public Date getDayLastUpdateWarned() {
        try {
            return getSdf().parse(dayLastUpdateWarned);
        } catch (ParseException e) {
            // Assume we've never warned
            return null;
        }
    }

    public void setDayLastInstallWarned() {
        getConfig().setProperty(DAY_LAST_INSTALL_WARNED, getSdf().format(new Date()));
        try {
            getConfig().save();
        } catch (ConfigurationException e) {
            LOGGER.error(e.getMessage(), e);
        }
    }

    public void setDayLastUpdateWarned() {
        getConfig().setProperty(DAY_LAST_UPDATE_WARNED, getSdf().format(new Date()));
        try {
            getConfig().save();
        } catch (ConfigurationException e) {
            LOGGER.error(e.getMessage(), e);
        }
    }

    public boolean isDownloadNewRelease() {
        return downloadNewRelease;
    }

    public void setDownloadNewRelease(boolean downloadNewRelease) {
        this.downloadNewRelease = downloadNewRelease;
        getConfig().setProperty(DOWNLOAD_NEW_RELEASE, this.downloadNewRelease);
    }

    public boolean isCheckAddonUpdates() {
        return checkAddonUpdates;
    }

    public void setCheckAddonUpdates(boolean checkAddonUpdates) {
        this.checkAddonUpdates = checkAddonUpdates;
        getConfig().setProperty(CHECK_ADDON_UPDATES, checkAddonUpdates);
    }

    public boolean isInstallAddonUpdates() {
        return installAddonUpdates;
    }

    public void setInstallAddonUpdates(boolean installAddonUpdates) {
        this.installAddonUpdates = installAddonUpdates;
        getConfig().setProperty(INSTALL_ADDON_UPDATES, installAddonUpdates);
    }

    public boolean isInstallScannerRules() {
        return installScannerRules;
    }

    public void setInstallScannerRules(boolean installScannerRules) {
        this.installScannerRules = installScannerRules;
        getConfig().setProperty(INSTALL_SCANNER_RULES, installScannerRules);
    }

    public boolean isReportReleaseAddons() {
        return reportReleaseAddons;
    }

    public void setReportReleaseAddons(boolean reportReleaseAddons) {
        this.reportReleaseAddons = reportReleaseAddons;
        getConfig().setProperty(REPORT_RELEASE_ADDON, reportReleaseAddons);
    }

    public boolean isReportBetaAddons() {
        return reportBetaAddons;
    }

    public void setReportBetaAddons(boolean reportBetaAddons) {
        this.reportBetaAddons = reportBetaAddons;
        getConfig().setProperty(REPORT_BETA_ADDON, reportBetaAddons);
    }

    public boolean isReportAlphaAddons() {
        return reportAlphaAddons;
    }

    public void setReportAlphaAddons(boolean reportAlphaAddons) {
        this.reportAlphaAddons = reportAlphaAddons;
        getConfig().setProperty(REPORT_ALPHA_ADDON, reportAlphaAddons);
    }

    public List<File> getAddonDirectories() {
        return addonDirectories;
    }

    public void setAddonDirectories(List<File> addonDirectories) {
        this.addonDirectories = addonDirectories;
        getConfig().setProperty(ADDON_DIRS, addonDirectories);
    }

    public File getDownloadDirectory() {
        return downloadDirectory;
    }

    public void setDownloadDirectory(File downloadDirectory) throws InvalidParameterException {
        setDownloadDirectory(downloadDirectory, true);
    }

    private void setDownloadDirectory(File downloadDirectory, boolean save)
            throws InvalidParameterException {
        if (!Constant.FOLDER_LOCAL_PLUGIN.equals(downloadDirectory.getAbsolutePath())) {
            // Check its one of the extra addon dirs
            boolean found = false;
            for (File f : this.addonDirectories) {
                if (f.equals(downloadDirectory)) {
                    found = true;
                    break;
                }
            }
            if (!found) {
                throw new InvalidParameterException(
                        "Directory must be the default one or one of the addonDirectories "
                                + downloadDirectory.getAbsolutePath());
            }
        }
        if (!downloadDirectory.canWrite()) {
            throw new InvalidParameterException(
                    "No write access to directory " + downloadDirectory.getAbsolutePath());
        }

        this.downloadDirectory = downloadDirectory;
        if (save) {
            getConfig().setProperty(DOWNLOAD_DIR, downloadDirectory.getAbsolutePath());
        }
    }
}
