/**
 * Copyright (C) 2019 Patrik Dufresne Service Logiciel <info@patrikdufresne.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of 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 com.planimod.ui;

import static com.planimod.ui.Localized._;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Locale;

import org.eclipse.core.databinding.observable.Realm;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.jface.databinding.swt.SWTObservables;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.preference.PreferenceStore;
import org.eclipse.jface.util.ILogger;
import org.eclipse.jface.util.Policy;
import org.eclipse.jface.window.Window;
import org.eclipse.jface.window.WindowManager;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Display;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.patrikdufresne.jface.dialogs.DetailMessageDialog;
import com.patrikdufresne.jface.dialogs.StatusHandlerDialog;
import com.patrikdufresne.managers.H2DBConfigurations;
import com.planimod.core.PlanimodManagers;
import com.planimod.ui.preference.PlanimodPreferences;
import com.planimod.ui.theme.Resources;

public class Main {

    /**
     * Define the application data directory name.
     */
    private static final String APP_USER_DATA = "planimod";

    private static Logger logger = LoggerFactory.getLogger(Main.class);

    /**
     * Preference store of the application.
     */
    private static PreferenceStore pref;

    /**
     * Define the preference file's name.
     */
    private static final String PREF_FILENAME = "preferences";

    private static void handleOpenDatabaseException(Exception exception) {
        // Display a generic message for other error.
        DetailMessageDialog
                .openDetailWarning(
                        null,
                        Display.getAppName(),
                        _("Une erreur s'est produite lors de l'ouverture de la base de données."),
                        _("Vérifiez qu'il est possible d'établir une communication avec l'ordinateur distante."),
                        exception);
    }

    /**
     * Display a human readable error message.
     * 
     * @param exception
     * @return True if the database should be created.
     */
    private static Boolean handleOpenDatabaseWithCreateTrialException(Exception exception) {
        // Check if the exception is causes by non-existing database. The only way I found is to parse the exception
        // message.
        Throwable cause = exception;
        while (cause != null) {
            if (cause.getMessage().contains("not found")) {
                DetailMessageDialog dlg = DetailMessageDialog
                        .openDetailYesNoQuestion(
                                null,
                                _("Ouverture de la base de donnée"),
                                _("Une erreur s'est produite lors de l'ouverture de la base de données."),
                                _("La base de données sélectionnée n'existe pas. Voulez vous créer cette nouvelle base de données ?"),
                                null);
                return IDialogConstants.YES_ID == dlg.getReturnCode();
            }
            cause = cause.getCause();
        }
        return null;
    }

    /**
     * Display a human readable error message.
     * 
     * @param exception
     * @return True if the database should be created.
     */
    private static Boolean handleOpenDatabaseWithReadonlyException(Exception exception) {
        // Try to determine if the exception is related to "ReadOnly" Access.
        // This occur when trying to open a readonly file with "servermode".
        Throwable cause = exception;
        while (cause != null) {
            if (cause.getMessage().contains("Feature not supported")
                    || cause.getMessage().contains("the database is read only")
                    || cause.getMessage().contains("Lock file exists")) {
                // Prompt user to open database in Read-only mode.
                DetailMessageDialog dlg = DetailMessageDialog
                        .openDetailYesNoQuestion(
                                null,
                                Display.getAppName(),
                                _("La base de données ne peut être ouverte en mode lecture-écriture. En ouvrir une copie ?"),
                                _(
                                        "Il se peut que vous n'ayez pas les droits suffisant pour ouvrir la base de données en mode lecture-écriture. Il vous est toutefois possible d'en ouvrir une copie. Toute modification apportée à cette copie ne peut être sauvegardée"),
                                null);
                return IDialogConstants.YES_ID == dlg.getReturnCode();
            }
            cause = cause.getCause();
        }
        // Try to determine if the exception is related to "ReadOnly" Access.
        // Or if the database is lock by another process.
        cause = exception;
        while (cause != null) {
            if (cause.getMessage().contains("Locked by another process") || cause.getMessage().contains("verrouillé")) {
                // Prompt user to open database in Read-only mode.
                DetailMessageDialog dlg = DetailMessageDialog
                        .openDetailYesNoQuestion(
                                null,
                                Display.getAppName(),
                                _("La base de données est verrouillée. Voulez-vous en ouvrir une copie ?"),
                                _(
                                        "La base de données semble être verrouillée par un autre utilisateur. Il vous est toutefois possible d'ouvrir une copie. Toute modification apportée à cette copie ne peut être sauvegardée."),
                                null);
                return IDialogConstants.YES_ID == dlg.getReturnCode();
            }
            cause = cause.getCause();
        }
        return null;
    }

    /**
     * Open the preference files.
     */
    public static void initPreference() {
        String osName = System.getProperty("os.name");
        String homeDir = System.getProperty("user.home");
        File prefFile;
        if (osName.equals("Linux")) {
            File configDir = new File(homeDir, ".config/" + APP_USER_DATA);
            configDir.mkdirs();
            prefFile = new File(configDir, PREF_FILENAME);
        } else {
            File configDir = new File(homeDir, APP_USER_DATA);
            configDir.mkdirs();
            prefFile = new File(configDir, PREF_FILENAME);
        }

        pref = new PreferenceStore(prefFile.getPath());
        try {
            pref.load();
        } catch (FileNotFoundException e) {
            // Swallow
        } catch (IOException e) {
            // Display error
            logger.error("Can't load the preferences.", e);
        }

        PlanimodPreferences.setPreferenceStore(pref);
    }

    /**
     * @param args
     */
    public static void main(String[] args) {

        logger.info("Planimod is starting...");

        // Init preferences
        initPreference();

        Display.setAppName(_("Planimod"));

        final Display display = new Display();
        Realm.runWithDefault(SWTObservables.getRealm(Display.getCurrent()), new Runnable() {
            @Override
            public void run() {

                updateJFacePolicy();

                // Set default application image to be use by all JFace
                // Window
                Window.setDefaultImages(new Image[] { Resources.getImage(Resources.APP_ICON_128), Resources.getImage(Resources.APP_ICON_48) });

                WindowManager winManager = new WindowManager();
                PlanimodManagers managers = null;
                try {
                    if ((managers = openDatabase()) == null) {
                        // Exit application
                        savePreference();
                        return;
                    }

                    // Open Main Window
                    PlanimodMainWindow win = new PlanimodMainWindow(null);
                    win.setManagers(managers);
                    win.setWindowManager(winManager);
                    win.open();

                    // Event loop
                    while (winManager.getWindowCount() > 0) {
                        try {
                            if (!display.readAndDispatch()) display.sleep();
                        } catch (RuntimeException e) {
                            PlanimodPolicy.showException(e);
                        }
                    }

                    savePreference();

                } catch (Throwable e) {
                    logger.error("Fatal error.", e);
                    DetailMessageDialog.openDetailWarning(null, _("Plannimod"), _("Une erreur fatale s'est produite."), null, e);
                } finally {
                    if (managers != null) {
                        managers.dispose();
                    }
                    winManager.close();
                    display.dispose();
                }
            }
        });
    }

    /**
     * Open a database. Returns True if a database got open. False if the operation have been cancel.
     */
    static PlanimodManagers openDatabase() {

        // Open DB selector
        PlanimodManagers managers = null;
        DatabaseSelectorDialog dlg = new DatabaseSelectorDialog(null);
        do {
            if (IDialogConstants.OK_ID != dlg.open() || dlg.getManagersConfig() == null) {
                // Operation cancel by user
                return null;
            }
            String url = dlg.getManagersConfig();
            try {
                // Try to open database in read-write mode.
                logger.info("Opening database: {}", url);
                managers = new PlanimodManagers(H2DBConfigurations.create(url, false, false));
                // Check if readonly.
                if (managers.isReadOnly()) {
                    throw new RuntimeException("the database is read only");
                }
            } catch (Exception e) {
                logger.error("Error opening the database connection", e);
                /*
                 * Check to open Readonly or Locked database.
                 */
                Boolean r = handleOpenDatabaseWithReadonlyException(e);
                if (r != null && !r) {
                    // Operation cancel by user
                    return null;
                } else if (r != null && r) {
                    try {
                        // Try to open a copy of database
                        managers = new PlanimodManagers(H2DBConfigurations.create(url, false, false, false, true));
                        managers.setIsCopy(true);
                        continue;
                    } catch (Exception e1) {
                        handleOpenDatabaseException(e1);
                    }
                }
                /*
                 * Check to create database.
                 */
                r = handleOpenDatabaseWithCreateTrialException(e);
                if (r != null && !r) {
                    // Operation cancel by user
                    return null;
                } else if (r != null && r) {
                    try {
                        // Try to create the database.
                        managers = new PlanimodManagers(H2DBConfigurations.create(url, true, false));
                        continue;
                    } catch (Exception e1) {
                        handleOpenDatabaseException(e1);
                    }
                }
                handleOpenDatabaseException(e);
            }
        } while (managers == null);

        return managers;
    }

    /**
     * Save the preferences.
     */
    public static void savePreference() {
        if (pref != null) {
            try {
                pref.save();
            } catch (IOException e) {
                // Print the error into logs
                logger.error("Can't save preferences.", e);
            }
        }
    }

    /**
     * This function sets the status handler.
     */
    protected static void updateJFacePolicy() {
        /**
         * Redirect all the Jface logger into SLF4J logger.
         */
        Policy.setLog(new ILogger() {

            /**
             * Create a default logger for ILogger.
             */
            final transient Logger logger = LoggerFactory.getLogger(getClass());

            /**
             * This implementation is logging the status object using the logger.
             */
            @Override
            public void log(IStatus status) {
                switch (status.getSeverity()) {
                case IStatus.OK:
                case IStatus.INFO:
                    if (status.getException() == null) {
                        logger.info(status.getMessage());
                    } else {
                        logger.info(status.getMessage(), status.getException());
                    }
                    break;
                case IStatus.WARNING:
                    if (status.getException() == null) {
                        logger.warn(status.getMessage());
                    } else {
                        logger.warn(status.getMessage(), status.getException());
                    }
                    break;
                case IStatus.ERROR:
                    if (status.getException() == null) {
                        logger.error(status.getMessage());
                    } else {
                        logger.error(status.getMessage(), status.getException());
                    }
                    break;
                }
            }
        });
        /**
         * Redirect all the JFace exception to our status dialog.
         */
        Policy.setStatusHandler(StatusHandlerDialog.getStatusHandler());
    }
}
