using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Windows;
using System.Threading;
using System.Runtime.InteropServices;
using System.Windows.Interop;
using System.Windows.Threading;

namespace TwoMbit
{
    /// <summary>
    /// Interaktionslogik für "App.xaml" a Multi Core Emulation Gui
    /// </summary>
    
    partial class App : Application {
        
        public static Dictionary<Cart._System, string> systemIdent = new Dictionary<Cart._System, string>();

        MainWindow mainwindow;
        Config cfg;
        Cart cart;
        Cart newCart; //load new Cart parallel to running one
        public Language lang = null;
        volatile Thread emu = null;
        Events events;

        bool _pause = false;

        IntPtr wih_viewport;
        IntPtr wih;

        public IntPtr viewHwndHandle {
            get { return wih_viewport; }
            set { wih_viewport = value; }
        }

        public IntPtr mainHwndHandle {
            get { return wih; }
            set { wih = value; }
        }

        public bool isCartLoaded() {
            return cart.isLoaded();
        }

        public static string APP_DIR = AppDomain.CurrentDomain.BaseDirectory;
        public const string CONFIG_FILE = "TwoMbit.ini";
        public const string LANGUAGE_DIR = "language/";
        public const string LANGUAGE_FILE = "english.xml";
        public const string APP_NAME = "TwoMbit 1.0 beta";

        public App()
        {
            systemIdent[Cart._System.SMS] = "sms";
            //SNES is only for private usage. I don't want to maintain two versions of the emulation gui.
            //systemIdent[Cart._System.SNES] = "snes";
            //systemIdent[Cart._System.GENESIS] = "genesis";

            try {
                cfg = new Config(APP_DIR + CONFIG_FILE);
                if (!cfg.load()) {
                    lang = new Language(LANGUAGE_FILE);
                    showWarning(lang.get("cfg_load"));
                } else {
                    lang = new Language(cfg.get<string>("language_file"));
                }
                cart = new Cart(this, cfg, lang);

                foreach (KeyValuePair<Cart._System, string> ident in systemIdent) {
                    new EmuSettings(cfg, ident.Key);
                    new Input(cfg, this, ident.Key);
                }

                emu = new Thread(new ThreadStart(run));
                emu.IsBackground = true;
                emu.Start();

                mainwindow = new MainWindow(this, cfg, lang);
                mainwindow.setTitle(App.APP_NAME);
                mainwindow.Show();
                mainHwndHandle = new WindowInteropHelper(mainwindow).Handle; //dsound, dinput
            } catch(Exception e) {
                showError(e.Message);
                kill();
            }
        }

        public static bool isSystemInitialized(Cart._System unit) {
            foreach (KeyValuePair<Cart._System, string> ident in systemIdent) {
                if (ident.Key == unit) return true;
            }
            return false;
        }

        public void loadRom(string fn, Cart._System unit) {
            newCart = new Cart(this, cfg, lang);
            if(newCart.load(fn, unit)) {
                fn = systemIdent[unit] + " | " + fn;
                mainwindow.addRecent(fn);
            }
        }

        public void raiseEvent (Events.Job job) {
            events.raise(job);
        }

        public void setTitle(string title) {
            mainwindow.setTitle(title);
        }

        bool checkCoreDll() {
            try {
                DLLCore.coreTest();
            } catch(DllNotFoundException) {
                showError(lang.get(systemIdent[newCart.getSystem()] + "_core_dll"));
                return false;
            }
            return true;
        }

        public void completeLoad() {
            DLLCore.assignCore(newCart.getSystem());
            if (!checkCoreDll()) { //emulation core dll not found
                DLLCore.assignCore(cart.getSystem());
                return;
            }
            setTitle(newCart.getName());
            mainwindow.updateVisibillity(TwoMbit.MainWindow.ItemVisibillity.LOAD);
            mainwindow.closeLoaderGui();
            DLLCore.setHwnd(mainHwndHandle);
            raiseEvent(Events.Job.LOAD);
        }

        public void startEmulation() {
            unload();
            cart = newCart;
            cart.pinning();
            updateSettings();

            switch (cart.getSystem()) {
                case Cart._System.GENESIS:
                    DLLCore.genSetCartMemory(cart.getMemoryRom().getData(), cart.getMemoryRom().getSize(),
                                        cart.getMemorySram().getData(), cart.getMemorySram().getSize() );
                    DLLCore.map(cart.getType(), cart.getCfgRegion(), 0);
                    break;
                case Cart._System.SMS:
                    DLLCore.smsSetCartMemory(cart.getMemoryRom().getData(), cart.getMemoryRom().getSize(),
                                        cart.getMemorySram().getData(), cart.getMemorySram().getSize(),
                                        cart.getMemorySlotArom().getData(), cart.getMemorySlotArom().getSize());
                    DLLCore.map(cart.getType(), cart.getCfgRegion(), (uint)cfg.get<int>("sms_unit"));
                    break;
                case Cart._System.SNES:
                    DLLCore.snesSetCartMemory(cart.getMemoryRom().getData(), cart.getMemoryRom().getSize(),
                                        cart.getMemorySram().getData(), cart.getMemorySram().getSize(),
                                        cart.getMemoryRtc().getData(), cart.getMemoryRtc().getSize(),
                                        cart.getMemorySlotArom().getData(), cart.getMemorySlotArom().getSize(),
                                        cart.getMemorySlotAsram().getData(), cart.getMemorySlotAsram().getSize(),
                                        cart.getMemorySlotBrom().getData(), cart.getMemorySlotBrom().getSize(),
                                        cart.getMemorySlotBsram().getData(), cart.getMemorySlotBsram().getSize() );
                    DLLCore.map(cart.getType(), cart.getCfgRegion(), 0);
                    break;
            }
            Input.getInputByIdent( cart.getSystem() ).init();
            power();
        }

        public void power() {
            DLLCore.power();
        }

        public void reset() {
            DLLCore.reset();
        }

        public void unload() {
            if (isCartLoaded()) {
                DLLCore.clearVideo();
                DLLCore.clearAudio();
            }
            cart.unload();
        }

        public void kill() {
            raiseEvent(Events.Job.UNLOAD);
            raiseEvent(Events.Job.EXIT);
            while (emu != null) {
                System.Threading.Thread.Sleep(1);
            }
            Current.Shutdown();
        }

        public void openLoaderGui(string fn, LoaderWindow.Type type) {
            mainwindow.openLoaderGui(fn, type);
        }

        public void updateVideo() {
            EmuSettings.updateResolutions();
            raiseEvent(Events.Job.SETTINGS);
        }

        public void updateScanline() {
            EmuSettings.updateScanlines();
            raiseEvent(Events.Job.SETTINGS);
        }

        public void updateFpsCounter() {
            EmuSettings.updateFpsCounters();
            raiseEvent(Events.Job.SETTINGS);
        }

        public void updateAudio() {
            EmuSettings.updateAudios();
            raiseEvent(Events.Job.SETTINGS);
        }

        public void updateSwFilter() {
            EmuSettings.updateSwFilters();
            raiseEvent(Events.Job.SETTINGS);
        }

        public void updateHwFilter() {
            EmuSettings.updateHwFilters();
            raiseEvent(Events.Job.SETTINGS);
        }

        public void loadFromLoaderGui(string slota, string slotb, LoaderWindow.Type type) {
            switch(type) {
                case LoaderWindow.Type.SUFAMI_TURBO:
                    newCart.loadSufamiTurbo(slota, slotb);
                    break;
                case LoaderWindow.Type.BSX:
                    newCart.loadBsx(slota);
                    break;
                case LoaderWindow.Type.BSX_SLOTTED:
                    newCart.loadBsxSlotted(slota);
                    break;
            }
        }

        public void updateHwndHost() {
            int width = 0, height = 0;
            mainwindow.getResolution(ref width, ref height);

            ControlHost chost = new ControlHost(height, width);
            mainwindow.ControlHostElement.Child = chost;
            viewHwndHandle = chost.hwndHostBox;
        }

        public void _initVideo() {
            mainwindow.Dispatcher.Invoke(DispatcherPriority.Normal,
                new DispatcherOperationCallback(delegate {
                bool fullscreen = cfg.get<bool>("fullscreen");
                //if (!fullscreen && !mainwindow.BackFromFullscreen()) updateHwndHost();

                updateHwndHost();
                mainHwndHandle = new WindowInteropHelper(mainwindow).Handle;
                
                int width = 0, height = 0;
                mainwindow.getResolution(ref width, ref height);
                if (!DLLCore.initVideo(fullscreen ? mainHwndHandle : viewHwndHandle, width, height, cfg.get<bool>("video_vsync"), fullscreen)) {
                    showError(lang.get("d3d_error"));
                    kill();
                }

                if (mainwindow.BackFromFullscreen()) {
                    mainwindow.resizeWindow();
                }

                return null;
            }), null);

            mainwindow.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
                new DispatcherOperationCallback(delegate {
                    if (mainwindow.BackFromFullscreen()) {
                        System.Threading.Thread.Sleep(400);
                        mainwindow._Window_SizeChanged();
                    }
                    return null;
            }), null);
        }

        void updateSettings() {
            if (!isCartLoaded()) return;

            EmuSettings _setting = EmuSettings.getSettingByIdent( cart.getSystem() );
            string sysIdent = systemIdent[cart.getSystem()];

            if (_setting.isResolutionUpdated()) {
                _initVideo();
            }
            if (_setting.isScanlineUpdated()) {
                DLLCore.setScanlines(cfg.get<bool>("video_scanlines"), (uint)cfg.get<int>("video_scanline_intensity"));
            }
            if (_setting.isSwFilterUpdated()) {
                DLLCore.updateColor(cfg.get<int>("video_contrast"), cfg.get<int>("video_gamma"), cfg.get<int>("video_brightness"), cfg.get<int>("video_filter_sw"), cfg.get<int>("ntsc_filter"), cfg.get<bool>("ntsc_filter_merge"));
            }
            if (_setting.isHwFilterUpdated()) {
                DLLCore.setHwFilter(cfg.get<int>("video_filter_hw"));
            }
            if (_setting.isAudioUpdated()) {
                if (!DLLCore.initSound(cfg.get<bool>("audio_active"), cfg.getSpeed(), (uint)cfg.get<int>(sysIdent + "_audio_frequency"), (uint)cfg.get<int>("audio_volume"))) {
                    showError(lang.get("dsound_error"));
                    kill();
                }
            }
            if (_setting.isFpsCounterUpdated()) {
                DLLCore.setCounter(cfg.get<bool>("show_fps"));
            }
        }

        public void pauseEmu() {
            if (!isCartLoaded()) return;
            _pause = true;
            DLLCore.pauseAudio();
        }

        public void unpauseEmu() {
            if (!isCartLoaded()) return;
            _pause = false;
            DLLCore.playAudio();
        }

        public void run() { //emulation worker
            events = new Events();
            while(true) {
                if (isCartLoaded() && !_pause) {
                    DLLCore.enter();
                } else {
                    System.Threading.Thread.Sleep(1); //idle
                }
                if (!processEvents()) {
                    break;
                }
            }
            emu = null;
        }

        bool processEvents() {

            Events.Job job = events.checkForUnfinishedJobs();

            switch(job) {
                default:
                case Events.Job.NOTHING:
                    return true;
                case Events.Job.LOAD:
                    startEmulation();
                    return processEvents();
                case Events.Job.UNLOAD:
                    unload();
                    return processEvents();
                case Events.Job.SETTINGS:
                    updateSettings();
                    return processEvents();
                case Events.Job.POWER:
                    power();
                    return processEvents();
                case Events.Job.RESET:
                    reset();
                    return processEvents();
                case Events.Job.EXIT:
                    return false;
            }
        }

        public void showError(string error) {
            MessageBox.Show(error, "Error");
        }

        public void showWarning(string warning) {
            MessageBox.Show(warning, "Warning");
        }

        ~App() {
            if (lang != null && !cfg.save()) {
                showWarning( lang.get("cfg_save"));
            }
        }

    }
}
