package me.teridax.jcash;

import me.teridax.jcash.banking.management.BankingManagementSystem;
import me.teridax.jcash.gui.IconProvider;
import me.teridax.jcash.gui.Loader;
import me.teridax.jcash.gui.Utils;
import me.teridax.jcash.gui.account.AccountController;
import me.teridax.jcash.gui.login.LoginController;
import me.teridax.jcash.lang.Locales;

import javax.swing.*;
import java.awt.*;
import java.io.IOException;
import java.util.Objects;
import java.util.logging.Level;

import static me.teridax.jcash.Logging.LOGGER;
import static me.teridax.jcash.Logging.initializeSystemLogger;
import static me.teridax.jcash.lang.Translator.translate;

public final class Main {

    private static final String LOGIN_SCREEN_STRING_IDENT = "LoginScreen";
    private static final String PROFILE_SCREEN_STRING_IDENT = "ProfileScreen";
    private static final String VERSION = "v2.0.0";

    /**
     * Main instance of this program. Contains the primary window.
     */
    private static Main instance;

    /**
     * Primary window of this program
     */
    private final JFrame window;
    private final CardLayout layout;

    private BankingManagementSystem bms;

    private LoginController loginMask;
    private AccountController accountController;

    private Main() {
        // create main window and set defaults
        this.window = new JFrame();
        this.window.setTitle(translate("Cashmachine") + getInfoString());
        this.window.setLocationByPlatform(true);
        this.window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.window.setIconImage(IconProvider.getWindowIcon());

        this.layout = new CardLayout();
        this.window.getContentPane().setLayout(this.layout);

        initialize();
    }

    private String getInfoString() {
        return " locale: [" + Locales.getDefaultLocale().toString() + "] " + VERSION;
    }

    private void initialize() {
        // create the login mask

        this.loginMask = new LoginController();

        // when we have logged in set the account viewer as window content
        this.loginMask.addAccountSelectionListener(account -> {
            LOGGER.finer("account selected: " + Objects.toString(account, "null"));
            accountController.setProfile(account, bms);
            layout.show(window.getContentPane(), PROFILE_SCREEN_STRING_IDENT);
        });

        this.window.getContentPane().add(loginMask.getView(), LOGIN_SCREEN_STRING_IDENT);

        // create the account viewer

        this.accountController = new AccountController();
        this.window.getContentPane().add(accountController.getView(), PROFILE_SCREEN_STRING_IDENT);
    }

    public void loadDatabase() {
        try {
            this.bms = Loader.load();
        } catch (Exception e) {
            LOGGER.severe("Failed to load database: " + e.getMessage());
            Utils.error("Failed to load database");
            System.exit(1);
        }
        this.loginMask.setBankingManagementSystem(bms);
        showLoginScreen();
        this.window.pack();
        this.window.setResizable(false);
        this.window.setLocationRelativeTo(null);
        this.window.setVisible(true);
    }

    public static void main(String[] args) {
        initializeSystemLogger(Level.FINE);

        setPlatformDependingTheme();

        loadExtraFont();

        Locales.autodetectDefaultLocale();

        // create main instance and show the login screen
        instance();
        getInstance().loadDatabase();
    }

    /**
     * Loads the extra font file used on the login button
     */
    private static void loadExtraFont() {
        try {
            var font = Font.createFont(Font.TRUETYPE_FONT, Objects.requireNonNull(IconProvider.class.getResourceAsStream("res/Circus.ttf")));
            GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
            ge.registerFont(font);
        } catch (IOException | FontFormatException | NullPointerException e) {
            LOGGER.warning("Could not load font file: " + e.getMessage());
        }
    }

    /**
     * Set the look and feel via the ui manager.
     * This function will select a look and feel so that the application will
     * look most like a native application.
     * It will select the systems look and feel for Windows and MacOS
     * and GTK for unix based systems.
     */
    private static void setPlatformDependingTheme() {
        // default look and feel
        var laf = UIManager.getCrossPlatformLookAndFeelClassName();

        // runtime os
        String os = System.getProperty("os.name").toLowerCase();
        LOGGER.fine("Detected operating system: " + os);

        // set look and feel class name depending on the platform we run on
        if (os.contains("win") || os.contains("mac")) {
            LOGGER.info("Detected Windows or MacOS based operating system, using system look and feel");
            laf = UIManager.getSystemLookAndFeelClassName();
        } else if (os.contains("nix") || os.contains("nux") || os.contains("aix") || os.contains("sunos")) {
            LOGGER.info("Detected Unix/Linux based operating system, using GTK look and feel");
            laf = "com.sun.java.swing.plaf.gtk.GTKLookAndFeel";
        } else {
            LOGGER.warning("Unable to detect operating system: " + os);
        }

        try {
            LOGGER.info("Setting look and feel to class: " + laf);
            UIManager.setLookAndFeel(laf);
        } catch (ClassNotFoundException | UnsupportedLookAndFeelException e) {
            LOGGER.severe("Look and feel class not found: " + e.getMessage());
        } catch (InstantiationException | IllegalAccessException e) {
            LOGGER.warning("Could not set look and feel: " + e.getMessage());
        }
    }

    /**
     * Get the main singleton instance of this program
     *
     * @return the singleton instance of this class
     */
    public static Main getInstance() {
        return instance;
    }

    /**
     * Attempts to create a new instance of the singleton class Main.
     * This method throws an exception if the class is already instantiated.
     *
     * @throws IllegalStateException if the class is already instantiated
     */
    public static void instance() {
        if (null != instance)
            throw new IllegalStateException(Main.class.getName() + " is already initialized");

        LOGGER.fine("Creating singleton instance of class " + Main.class.getName());

        Main.instance = new Main();
    }

    /**
     * Shows the open dialog for selecting a database file. After selection, it then proceeds to prompt login.
     * Afterward the selected account can be managed.
     * This method is non-blocking and all work described is performed asynchronously on the AWT Event dispatcher.
     */
    public void showLoginScreen() {
        this.layout.show(this.window.getContentPane(), LOGIN_SCREEN_STRING_IDENT);
    }

    /**
     * Logs the user out of the database, hiding the main window.
     */
    public void logout() {
        this.loginMask.logout();
        this.layout.show(this.window.getContentPane(), LOGIN_SCREEN_STRING_IDENT);
    }

    public JFrame getWindow() {
        return this.window;
    }
}