/*
 * Copyright (C) 2018-2020, 2022 Andrew Gegg
 *
 *	This file is part of the Garden Notebook application
 *
 * The Garden Notebook application 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, either version 3 of the License, or
 * (at your option) any later version.
 *
 * 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, see <http://www.gnu.org/licenses/gpl.html>.
 */

/*
	Change log
	2.1.0   Rearranged Preferences
            Implement some command-line parameters
	2.4.0   Include a version string available to config, etc.
    2.6.1   Log the current environment (previously in NotebookConfig)
    2.8.1   Code tidy
            Make 'JSON dump on exit' true by default
    3.0.6   Add --env command-line option to facilitate Pi port
 */

package uk.co.gardennotebook;

import java.io.IOException;
import java.text.MessageFormat;
import java.util.List;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.ButtonType;
import javafx.scene.image.Image;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
//import org.apache.logging.log4j.Level;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
//import org.apache.logging.log4j.core.Filter;
//import org.apache.logging.log4j.core.LoggerContext;
//import org.apache.logging.log4j.core.appender.RollingFileAppender;
//import org.apache.logging.log4j.core.appender.rolling.DefaultRolloverStrategy;
//import org.apache.logging.log4j.core.appender.rolling.OnStartupTriggeringPolicy;
//import org.apache.logging.log4j.core.appender.rolling.action.Action;
import org.apache.logging.log4j.message.EntryMessage;
//import org.apache.logging.log4j.core.config.Configuration;
//import org.apache.logging.log4j.core.config.DefaultConfiguration;
//import org.apache.logging.log4j.core.filter.ThresholdFilter;
//import org.apache.logging.log4j.core.layout.PatternLayout;
import uk.co.gardennotebook.spi.GNDBException;
import uk.co.gardennotebook.spi.ITrug;
import uk.co.gardennotebook.spi.TrugServer;

/**
 *	Entry point for the application from the command line (or equivalent).
 * 
 * @author Andrew Gegg
*	@version	3.0.6
*	@since	1.0
 */
public class GardenNotebook extends Application
{
    private static final Logger LOGGER = LogManager.getLogger();

    static final String NOTEBOOK_VERSION = "3.2.1"; //  2.4.0 - accessed elsewhere, keep as 'package private'

    private Preferences prefs = Preferences.userNodeForPackage(GardenNotebook.class);

    private boolean trugKnown = false;
    private boolean serverKnown = false;
    private boolean serviceCached = false;

    private String selectedServer = "";

    private boolean loginThisTime = false;

    @Override
    public void start(Stage stage) throws Exception
    {
        // NB This commented out code uses interfaces in log4j INTERNAL classes which are liable to change on any update, however minor
//		LoggerContext ctx = (LoggerContext)LogManager.getContext(false);
//		Configuration cfg = ctx.getConfiguration();
////		System.out.println("config name: "+cfg.getName());
//		if (cfg.getName().startsWith(DefaultConfiguration.DEFAULT_NAME))
//		{
////			System.out.println("Attempting to add appender!");
//			
//			RollingFileAppender app = RollingFileAppender.newBuilder().
//				withName("RollingFile").
//				withLayout(PatternLayout.newBuilder().withPattern("from extra appender: %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n").build()).
////				withFilter(ThresholdFilter.createFilter(Level.TRACE, Filter.Result.ACCEPT, Filter.Result.ACCEPT)).
//				withFileName("logs/GardenNotebook.log").
//				withFilePattern("logs/$${date:yyyy-MM}/GardenNotebook-%d{MM-dd-yyyy}-%i.log").
//				withPolicy(OnStartupTriggeringPolicy.createPolicy(1)).
////				withStrategy(DefaultRolloverStrategy.createStrategy("3", "1", "max", "0", null, true, cfg)).
//				withStrategy(DefaultRolloverStrategy.newBuilder().
//                                                    withMax("3").
//                                                    withMin("1").
////                                                    withFileIndex("max").     // max is the default
//                                                    withCompressionLevelStr("0").
////                                                    withStopCustomActionsOnError(true).   // no custom actions so irrelevant
//                                                    withConfig(cfg).
//                                                    build() ).
//				build();
////			System.out.println("Appender: "+app);
//			cfg.addAppender(app);
//			ctx.updateLoggers();
//		}


        //  2.6.1 - log the working environment (used to be in config - but only on login!)
        logEnvironment();

        // decide if we need a specific login
        boolean firstTime = prefs.getBoolean("firstTime", true);
        boolean requireLogin = prefs.getBoolean("requireLogin", true);
        LOGGER.debug("start(): firstTime: {}, requireLogin: {}", firstTime, requireLogin);

        if (!firstTime && !requireLogin)
        {//need to log in if no server has been selected or its parameters are not cached
            String selectedTrug = prefs.get("selectedTrug", "");
            LOGGER.debug("start(): selectedTrug: {}", selectedTrug);
            trugKnown = !selectedTrug.isBlank();
            if (trugKnown)
            {
                selectedServer = prefs.node(selectedTrug).get("selectedServer", "");
            }
            serverKnown = !selectedServer.isBlank();
            if (serverKnown)
            {
                serviceCached = prefs.node(selectedTrug).node(selectedServer).getBoolean("cached", false);
            }
            LOGGER.debug("start(): selectedServer: {},serviceCached: {}", selectedServer, serviceCached);
        }

        if (loginThisTime || firstTime || requireLogin || !trugKnown || !serverKnown || !serviceCached)
        {// need to use the Config screen as a login
            showConfigPopup(stage);
        }
        else
        {
            // get a DB server up and running
            String checkVal = TrugServer.getTrugServer().checkTrug(prefs);
            LOGGER.debug("starting app: check: " + checkVal);
            if (!"OK".equals(checkVal))
            {
                LOGGER.debug("starting app: service check failed");
                ResourceBundle resources = ResourceBundle.getBundle("notebook");
                Alert checkConnect = new Alert(Alert.AlertType.ERROR, checkVal, ButtonType.OK);
                checkConnect.setTitle(resources.getString("alert.config.cannotconnect"));
                Optional<ButtonType> result = checkConnect.showAndWait();
                return;
            }
            showSplashAndEnter(stage);
        }
        stage.show();
    }

    //  2.8.1
    private void showSplashAndEnter(Stage stage)
    {
        Parent root = new GardenSplash();
        Scene scene = new Scene(root);
        stage.initStyle(StageStyle.UNDECORATED);
        LOGGER.debug("before loading icons");
        loadIcons(stage);
        LOGGER.debug("after loading icons");
        stage.setScene(scene);
        stage.sizeToScene();
    }

    //  2.8.1
    private void showConfigPopup(Stage stage)
    {
        // need to use the Config screen as a login
        Parent root = new NotebookConfig();
        Scene scene = new Scene(root);

        ResourceBundle resources = ResourceBundle.getBundle("notebook");
        stage.setTitle(String.format(resources.getString("app.title"), NOTEBOOK_VERSION));  //  2.4.0
        loadIcons(stage);
        stage.setScene(scene);
    }

    //  2.8.1
    private void loadIcons(Stage stage)
    {
        stage.getIcons().add(new Image(GardenNotebook.class.getResource("/Icon16.png").toExternalForm()));
        stage.getIcons().add(new Image(GardenNotebook.class.getResource("/Icon32.png").toExternalForm()));
        stage.getIcons().add(new Image(GardenNotebook.class.getResource("/Icon64.png").toExternalForm()));
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args)
    {
        launch(args);
    }

    @Override
    public void init()
    {// handle run-time parameters here
        List<String> unNamed = getParameters().getUnnamed();
        if (unNamed.contains("--version"))
        {
            System.out.println(NOTEBOOK_VERSION);
            Platform.exit();    //  this is NOT guaranteed to work here!
        }
        if (unNamed.contains("--env"))
        {
            printEnvironment();
            Platform.exit();    //  this is NOT guaranteed to work here!
        }
        if (unNamed.contains("--reset"))
        {
            clearPrefs(prefs);
            prefs = Preferences.userNodeForPackage(GardenNotebook.class);
        }
        else if (unNamed.contains("--login"))
        {
            loginThisTime = true;
        }
        else if (unNamed.contains("--msi"))
        {// installed via .msi which has already forced acceptance of licence terms
            prefs.putBoolean("licenceAgreed", true);
        }
    }

    @Override
    public void stop()
    {
        EntryMessage log4jEntryMsg = LOGGER.traceEntry("Application stop()");

        final TrugServer server = TrugServer.getTrugServer();
        LOGGER.debug("server: {}", server);
        if (server == null)
            return;
        final ITrug trug = server.getTrug();
        LOGGER.debug("trug: {}", trug);
        if (trug == null)
            return;

        try
        {
            if (prefs.nodeExists("JSON"))
            {// node will not exist if we've just reset to factory values
                Preferences jp = prefs.node("JSON");
                LOGGER.debug("stop: prefs: JSON/dumpOnExit: {}, dump dir: {}", jp.getBoolean("dumpOnExit", false), jp.get("dumpDir", "JSONDumpDir"));
                if (jp.getBoolean("dumpOnExit", true))  //  2.8.1
                {
                    try
                    {
                        trug.toJson(jp);
                    }
                    catch (IOException ex)
                    {
                        LOGGER.error("The JSON Dump Directory {} is not a directory", jp.get("dumpDir", "JSONDumpDir"));
                        ResourceBundle resources = ResourceBundle.getBundle("notebook");
                        Alert errBox = new Alert(Alert.AlertType.ERROR, MessageFormat.format(resources.getString("alert.config.dumpdirectoryinvalid"), jp.get("dumpDir", "JSONDumpDir")), ButtonType.OK);
                        errBox.showAndWait();
                    }
                    catch (GNDBException ex)
                    {
                        PanicHandler.panic(ex);
                    }
                }
            }
        }
        catch (BackingStoreException ex)
        {
            // at this point we really don't care
        }
        catch (IllegalStateException ignored)
        {

        }
        // and close down any databases, etc
        trug.close();

        LOGGER.info("GardenNotebook application stopped.");

        LOGGER.traceExit(log4jEntryMsg);
    }

    private void clearPrefs(Preferences prefs)
    {
        EntryMessage log4jEntryMsg = LOGGER.traceEntry("Application: clearPrefs()");
        try
        {
            prefs.removeNode();
            prefs.flush();
            prefs = null;
        }
        catch (BackingStoreException ex)
        {
            LOGGER.error("Failed to remove Preferences node: exception: {}", ex.toString());
        }
        LOGGER.traceExit(log4jEntryMsg);
    }

    //  2.6.1
    private void logEnvironment()
    {
        LOGGER.info("GardenNotebook version {}", NOTEBOOK_VERSION);
        LOGGER.info("OS: {}, version: {}, architecture: {}", System.getProperty("os.name"), System.getProperty("os.version"), System.getProperty("os.arch"));
        LOGGER.info("Java version: {}, date: {}", System.getProperty("java.version"), System.getProperty("java.version.date"));
        LOGGER.info("Java vendor: {}", System.getProperty("java.vendor"));
        LOGGER.debug("AppData: {}", System.getenv("APPDATA"));
        LOGGER.debug("Local AppData: {}", System.getenv("LOCALAPPDATA"));
        LOGGER.info("User name: {}", System.getProperty("user.name"));
        LOGGER.info("User home directory: {}", System.getProperty("user.home"));
        LOGGER.info("User working directory: {}", System.getProperty("user.dir"));
        LOGGER.debug("File separator: {}", System.getProperty("file.separator"));

        LOGGER.debug("CLASSPATH environment variable: {}", System.getenv("CLASSPATH"));
        LOGGER.debug("class.path property: {}", System.getProperty("java.class.path"));

        LOGGER.debug("log4j2 version: {}", org.apache.logging.log4j.util.PropertiesUtil.class.getPackage().getImplementationVersion());
    }

    /**
     * @since 3.0.6
     */
    private void printEnvironment()
    {
        System.out.println("GardenNotebook version " + NOTEBOOK_VERSION);
        System.out.println("OS: " + System.getProperty("os.name") + ", version: " + System.getProperty("os.version") + ", architecture: " + System.getProperty("os.arch"));
        System.out.println("Java version: " + System.getProperty("java.version") + ", date: " + System.getProperty("java.version.date"));
        System.out.println("Java vendor: " + System.getProperty("java.vendor"));
        System.out.println("AppData: " + System.getenv("APPDATA"));
        System.out.println("Local AppData: " + System.getenv("LOCALAPPDATA"));
        System.out.println("User name: " + System.getProperty("user.name"));
        System.out.println("User home directory: " + System.getProperty("user.home"));
        System.out.println("User working directory: " + System.getProperty("user.dir"));
        System.out.println("File separator: " + System.getProperty("file.separator"));

        System.out.println("CLASSPATH environment variable: " + System.getenv("CLASSPATH"));
        System.out.println("class.path property: " + System.getProperty("java.class.path"));

        System.out.println("log4j2 version: " + org.apache.logging.log4j.util.PropertiesUtil.class.getPackage().getImplementationVersion());
    }

}