//
//  CelestiaCore.mm
//  celestia
//
//  Created by Bob Ippolito on Wed Jun 05 2002.
//  Copyright (C) 2007, Celestia Development Team
//

#import "CelestiaAppCore.h"
#import "CelestiaAppCore_PrivateAPI.h"
#import "NSString_ObjCPlusPlus.h"
#import "CelestiaFavorite_PrivateAPI.h"
#import "CelestiaSelection_PrivateAPI.h"
#import "CelestiaSimulation_PrivateAPI.h"
#import "CelestiaRenderer_PrivateAPI.h"
#import "Astro.h"

#import "CelestiaController.h"
#include "celestiacore.h"
#include "render.h"
#include "qtcapture.h"

class MacSettingsWatcher : public CelestiaWatcher, public RendererWatcher
{
private:
    CelestiaSettings *settings;
    CelestiaCore *appCore;

public:
    MacSettingsWatcher(CelestiaAppCore *_appCore,
                       CelestiaSettings* _settings) :
        CelestiaWatcher(*[_appCore appCore]), settings(_settings), appCore([_appCore appCore])
    {
        appCore->getRenderer()->addWatcher(this);
    };
    
    virtual ~MacSettingsWatcher()
    {
        appCore->getRenderer()->removeWatcher(this);
    };

    void notifyChange(CelestiaCore *, int flags)
    {
        if ( 0 != (flags & (
//              CelestiaCore::LabelFlagsChanged
//            | CelestiaCore::RenderFlagsChanged
            CelestiaCore::VerbosityLevelChanged
            | CelestiaCore::TimeZoneChanged
//            | CelestiaCore::AmbientLightChanged
            | CelestiaCore::FaintestChanged
            )) )
        {
            [settings validateItems];
        }
    };

    virtual void notifyRenderSettingsChanged(const Renderer *renderer)
    {
        [settings validateItems];
    };
};

class MacOSXAlerter : public CelestiaCore::Alerter
{
public:
    MacOSXAlerter() {};
    virtual ~MacOSXAlerter() {};

    virtual void fatalError(const std::string& msg)
    {
        @autoreleasepool {
            NSLog(@"alerter fatalError!");
            [[CelestiaController shared] fatalError: [NSString stringWithStdString: msg] ];
            NSLog(@"alerter fatalError finis");
        }
    }
};

class MacOSXSplashProgressNotifier : public ProgressNotifier
{
public:
    MacOSXSplashProgressNotifier() {};
    virtual ~MacOSXSplashProgressNotifier() {};

    virtual void update(const string& msg)
    {
        @autoreleasepool {
            id splash = [[CelestiaController shared] valueForKey: @"splashWindowController"];
            if (splash && [splash respondsToSelector:@selector(setStatusText:)]) {
                [splash performSelectorOnMainThread:@selector(setStatusText:) withObject:[NSString stringWithStdString: msg] waitUntilDone:NO];
            }
        }
    };
};

class MacOSXCursorHandler : public CelestiaCore::CursorHandler
{
public:
    MacOSXCursorHandler() : shape(CelestiaCore::ArrowCursor) {};
    virtual ~MacOSXCursorHandler() {};
    virtual void setCursorShape(CelestiaCore::CursorShape aShape)
    {
        if (aShape != shape)
        {
            switch (aShape)
            {
            case CelestiaCore::SizeVerCursor:
                [[NSCursor resizeUpDownCursor] set];
                break;
            case CelestiaCore::SizeHorCursor:
                [[NSCursor resizeLeftRightCursor] set];
                break;
            default:
                [[NSCursor arrowCursor] set];
                break;
            }
            shape = aShape;
        }
    };

    virtual CelestiaCore::CursorShape getCursorShape() const
    {
        return shape;
    };
    
private:
    CelestiaCore::CursorShape shape;
};


CelestiaAppCore *_sharedCelestiaAppCore;
CelestiaCore *appCore;

@implementation CelestiaAppCore

-(CelestiaCore*) appCore
    {return appCore; };

-(int)toCelestiaKey:(NSEvent*)theEvent {

    int	celestiaKey = 0;
    unichar key = [[theEvent characters] characterAtIndex: 0];

   if ( ([theEvent modifierFlags] & NSNumericPadKeyMask) && (key >= '0') && (key <= '9'))
    switch(key)
    {
        case '0':
            celestiaKey = CelestiaCore::Key_NumPad0;
            break;
        case '1':
            celestiaKey = CelestiaCore::Key_NumPad1;
            break;
        case '2':
            celestiaKey = CelestiaCore::Key_NumPad2;
            break;
        case '3':
            celestiaKey = CelestiaCore::Key_NumPad3;
            break;
        case '4':
            celestiaKey = CelestiaCore::Key_NumPad4;
            break;
        case '5':
            celestiaKey = CelestiaCore::Key_NumPad5;
            break;
        case '6':
            celestiaKey = CelestiaCore::Key_NumPad6;
            break;
        case '7':
            celestiaKey = CelestiaCore::Key_NumPad7;
            break;
        case '8':
            celestiaKey = CelestiaCore::Key_NumPad8;
            break;
        case '9':
            celestiaKey = CelestiaCore::Key_NumPad9;
            break;
        default: break;
        }
        else switch(key)
        {
        case NSF1FunctionKey:
            celestiaKey = CelestiaCore::Key_F1;
            break;
        case NSF2FunctionKey:
            celestiaKey = CelestiaCore::Key_F2;
            break;
        case NSF3FunctionKey:
            celestiaKey = CelestiaCore::Key_F3;
            break;
        case NSF4FunctionKey:
            celestiaKey = CelestiaCore::Key_F4;
            break;
        case NSF5FunctionKey:
            celestiaKey = CelestiaCore::Key_F5;
            break;
        case NSF6FunctionKey:
            celestiaKey = CelestiaCore::Key_F6;
            break;
        case NSF7FunctionKey:
            celestiaKey = CelestiaCore::Key_F7;
            break;
        case NSF8FunctionKey:
            celestiaKey = CelestiaCore::Key_F8;
            break;
        case NSF9FunctionKey:
            celestiaKey = CelestiaCore::Key_F9;
            break;
        case NSF10FunctionKey:
            celestiaKey = CelestiaCore::Key_F10;
            break;
        case NSF11FunctionKey:
            celestiaKey = CelestiaCore::Key_F11;
            break;
        case NSF12FunctionKey:
            celestiaKey = CelestiaCore::Key_F12;
            break;
        case NSUpArrowFunctionKey:
            celestiaKey = CelestiaCore::Key_Up;
            break;
        case NSDownArrowFunctionKey:
            celestiaKey = CelestiaCore::Key_Down;
            break;
        case NSLeftArrowFunctionKey:
            celestiaKey = CelestiaCore::Key_Left;
            break;
        case NSRightArrowFunctionKey:
            celestiaKey = CelestiaCore::Key_Right;
            break;
        case NSPageUpFunctionKey:
            celestiaKey = CelestiaCore::Key_PageUp;
            break;
        case NSPageDownFunctionKey:
            celestiaKey = CelestiaCore::Key_PageDown;
            break;
        case NSHomeFunctionKey:
            celestiaKey = CelestiaCore::Key_Home;
            break;
        case NSEndFunctionKey:
            celestiaKey = CelestiaCore::Key_End;
            break;
        case NSInsertFunctionKey:
            celestiaKey = CelestiaCore::Key_Insert;
            break;
        default:
            if ((key < 128) && (key > 33))
            {
                celestiaKey = (int) (key & 0x00FF);
            }
            break;
    }

    return celestiaKey;
}

-(int)toCelestiaModifiers:(NSEventModifierFlags)modifiers buttons:(unsigned int)buttons {
    int cModifiers = 0;
    if (modifiers & NSCommandKeyMask)
        cModifiers |= CelestiaCore::ControlKey;
    if (modifiers & NSControlKeyMask)
        cModifiers |= CelestiaCore::ControlKey;
    if (modifiers & NSShiftKeyMask)
        cModifiers |= CelestiaCore::ShiftKey;
    if (buttons & 1)
        cModifiers |= CelestiaCore::LeftButton;
    if (buttons & 2)
        cModifiers |= CelestiaCore::MiddleButton;
    if (buttons & 4)
        cModifiers |= CelestiaCore::RightButton;
    return cModifiers;
}


+(void)initialize
{
    _sharedCelestiaAppCore = nil;
    appCore = NULL;
}

+(CelestiaAppCore *)sharedAppCore
{
    if (_sharedCelestiaAppCore != nil) return _sharedCelestiaAppCore;
    _sharedCelestiaAppCore = [[CelestiaAppCore alloc] init];
    return _sharedCelestiaAppCore;
}
    
-(CelestiaAppCore *)init
{
    if (_sharedCelestiaAppCore != nil) {
        return _sharedCelestiaAppCore;
    }
    self = [super init];
    appCore = NULL;
    return self;
}
-(void)archive
{
    //NSLog(@"[CelestiaAppCore archive]");
    [[CelestiaFavorites sharedFavorites] archive];
    [[self renderer] archive];
}

- (void)dealloc
{
    if (appCore != NULL) {
        // appCore doesn't own the custom alerter and cursor
        // handler for some reason so we assume responsibility
        // for cleanup
        if (appCore->getAlerter())
            delete appCore->getAlerter();
        if (appCore->getCursorHandler())
            delete appCore->getCursorHandler();
        delete appCore;
        appCore = NULL;
    }
    _sharedCelestiaAppCore = nil;
}

-(BOOL)initSimulationWithConfigPath:(NSString *)configPath extraPath:(NSString *)extraPath
{
    BOOL result = NO;
    appCore = new CelestiaCore();
    std::string confFile = configPath ? [configPath UTF8String] : "";
    std::vector<std::string> extrasDirs;
    MacOSXSplashProgressNotifier progressNotifier;

    if (extraPath)
    {
        extrasDirs.push_back([extraPath UTF8String]);
    }

    appCore->setCursorHandler(new MacOSXCursorHandler());
    result = appCore->initSimulation(confFile.empty() ? NULL : &confFile,
                                     &extrasDirs,
                                     &progressNotifier);
    if (result)
    {
        CelestiaSettings *settings = [CelestiaSettings shared];
        new MacSettingsWatcher(self, settings); // adds itself to the appCore

        appCore->setAlerter(new MacOSXAlerter());
    }

    return result;
}

-(BOOL)initRenderer
{
    return (BOOL)appCore->initRenderer();
}

-(void)start:(NSDate *)date
{
    appCore->start([[Astro julianDate:date] doubleValue]);
}

-(void)charEntered:(char)c withModifiers:(int)modifiers
{
// moved to CelestiaOpenGLView...    if (c == 127) c = 8; 
    appCore->charEntered(c, modifiers);
}

-(void)charEntered:(NSString *)string
{
    appCore->charEntered([string UTF8String]);
}

-(void)keyDown:(int)c
{
    appCore->keyDown(c);
}

-(void)keyUp:(int)c
{
    appCore->keyUp(c);
}

-(void)keyDown:(int)c withModifiers:(int)m
{
    appCore->keyDown(c,m);
}

-(void)keyUp:(int)c withModifiers:(int)m
{
    appCore->keyUp(c,m);
}

-(void)mouseWheel:(float)motion modifiers:(int)modifiers
{
    appCore->mouseWheel(motion, modifiers);
}

-(void)mouseButtonDown:(NSPoint)coord modifiers:(int)modifiers
{
    appCore->mouseButtonDown(coord.x,coord.y,modifiers);
}

-(void)mouseButtonUp:(NSPoint)coord modifiers:(int)modifiers
{
    appCore->mouseButtonUp(coord.x,coord.y,modifiers);
}

-(void)mouseMove:(NSPoint)coord
{
    appCore->mouseMove(coord.x,coord.y);
}

-(void)mouseMove:(NSPoint)delta modifiers:(int)modifiers
{
    appCore->mouseMove(delta.x,delta.y,modifiers);
}

-(void)joystickAxis:(int)axis value:(float)value
{
    appCore->joystickAxis(axis,value);
}

-(void)joystickButton:(int)button state:(BOOL)state
{
    appCore->joystickButton(button,(bool)state);
}

-(void)resize:(NSRect)r
{
    appCore->resize((GLsizei)r.size.width,(GLsizei)r.size.height);
}

-(void)draw
{
    appCore->draw();
}

-(void)tick
{
    appCore->tick();
}

-(CelestiaSimulation *)simulation
{
    return [[CelestiaSimulation alloc] initWithSimulation:appCore->getSimulation()];
}

-(CelestiaRenderer *)renderer
{
    return [[CelestiaRenderer alloc] initWithRenderer:appCore->getRenderer()];
}

-(void)showText:(NSString *)text
{
    appCore->showText([text stdString]);
}

-(void)activateFavorite:(id)fav
{
    if ([fav isKindOfClass:[NSMenuItem class]])
        fav = [(NSMenuItem*)fav representedObject];
    appCore->activateFavorite(*[(CelestiaFavorite*)fav favorite]);
}

-(CelestiaFavorites *)favorites
{
    return [CelestiaFavorites sharedFavorites];
}

-(NSTimeZone *)timeZone
{
    NSTimeZone *bestZone=nil;
    bestZone = [NSTimeZone timeZoneWithAbbreviation:[NSString stringWithStdString:appCore->getTimeZoneName()]];
    if (bestZone == nil) 
      bestZone = [NSTimeZone timeZoneForSecondsFromGMT:appCore->getTimeZoneBias()];
    return bestZone;
}

-(void)setTimeZone:(NSTimeZone *)timeZone withDate:(NSDate *)date
{
    appCore->setTimeZoneBias((int)[timeZone secondsFromGMTForDate:date]);
    appCore->setTimeZoneName([[timeZone abbreviationForDate:date] stdString]);
}

-(int)textEnterMode
{
    return appCore->getTextEnterMode();
}

-(void)cancelScript
{
    appCore->cancelScript();
}

-(int)hudDetail
{
    return appCore->getHudDetail();
}

-(void)setHudDetail:(int)hudDetail
{
    appCore->setHudDetail(hudDetail);
}

-(void)back
{
    appCore->back();
}

-(void)forward
{
    std::vector<Url>::size_type historySize = appCore->getHistory().size();
    if (historySize < 2) return;
    if (appCore->getHistoryCurrent() > historySize-2) return;
    appCore->forward();
}

-(NSString *) currentURL
{
    CelestiaState appState;
    appState.captureState(appCore);
    Url currentUrl(appState, Url::CurrentVersion);
    
    NSString *url = [ NSString stringWithStdString: currentUrl.getAsString() ];
    return url;
}

-(void)goToUrl:(NSString *)url
{
    appCore->goToUrl([url stdString]);
}

-(void)setStartURL:(NSString *)url
{
    appCore->setStartURL([url stdString]);
}

-(unsigned int) getLocationFilter
{
    return appCore->getSimulation()->getActiveObserver()->getLocationFilter();
}

-(void) setLocationFilter: (unsigned int) filter
{
    appCore->getSimulation()->getActiveObserver()->setLocationFilter(filter);
}

-(void)runScript:(NSString *)fileName
{
    appCore->runScript([fileName stdString]);
}

- (void) showInfoURL
{
    Selection sel = appCore->getSimulation()->getSelection();

    string url;
    switch (sel.getType())
    {
    case Selection::Type_Body:
        {
            url = sel.body()->getInfoURL();
            if (url.empty())
            {
                string name = sel.body()->getName();
                for (int i = 0; i < (int)name.size(); i++)
                    name[i] = tolower(name[i]);

                url = string("http://www.nineplanets.org/") + name + ".html";
            }
        }
        break;

    case Selection::Type_Star:
        {
            url = sel.star()->getInfoURL();
            if (url.empty())
            {
                char name[32];
                sprintf(name, "HIP%d", sel.star()->getCatalogNumber() & ~0xf0000000);                
                url = string("http://simbad.u-strasbg.fr/sim-id.pl?protocol=html&Ident=") + name;
            }
        }
        break;

    case Selection::Type_DeepSky:
        url = sel.deepsky()->getInfoURL();
        break;

    case Selection::Type_Location:
        break;

    default:
        break;
    }


    if (!url.empty())
    {
        NSString *unescUrl = [NSString stringWithStdString: url];
        NSString *escUrl = [unescUrl stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];

        if (escUrl)
        {
            if ([escUrl length] > 0)
            {
                NSURL *theURL = [NSURL URLWithString: escUrl];

                if (theURL != nil)
                {
                    if (nil == [theURL scheme])
                        theURL = [NSURL fileURLWithPath: unescUrl];
                    if (theURL)
                        [[NSWorkspace sharedWorkspace] openURL: theURL];
                }
            }

        }
    }
}

-(unsigned int)aaSamples
{
    return appCore->getConfig()->aaSamples;
}

-(BOOL)glExtensionIgnored:(NSString *)extension
{
    CelestiaConfig *cfg = appCore->getConfig();
    return std::find(cfg->ignoreGLExtensions.begin(), cfg->ignoreGLExtensions.end(), [extension stdString]) != cfg->ignoreGLExtensions.end();
}

-(void)setGLExtensionIgnored:(NSString *)extension
{
    appCore->getConfig()->ignoreGLExtensions.push_back([extension stdString]);
}

- (BOOL) captureMovie: (NSString*) filename width: (int) width height: (int) height
                              frameRate: (float) framerate
{
    MovieCapture* movieCapture = new QTCapture();

    bool success = movieCapture->start([filename stdString], width, height, framerate);
    if (success)
        appCore->initMovieCapture(movieCapture);
    else
        delete movieCapture;

    return success;
}

@end
