/*
 * Copyright (C) 2004 Apple Computer, Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 */

#import "KWQObject.h"

#import "KWQVariant.h"
#import "KWQAssertions.h"

// The Foundation-level Cocoa calls here (NSTimer, NSDate, NSArray,
// NSDictionary) should be exception-free, so no need to block
// exceptions.

const QObject *QObject::_sender;
bool QObject::_defersTimers;

static CFMutableDictionaryRef timerDictionaries;
static CFMutableDictionaryRef allPausedTimers;
static NSMutableArray *deferredTimers;
static bool deferringTimers;

@interface KWQObjectTimerTarget : NSObject
{
    QObject *target;
    int timerId;
    NSTimeInterval remainingTime;
}

- initWithQObject:(QObject *)object timerId:(int)timerId;
- (void)timerFired;

@end

KWQSignal *QObject::findSignal(const char *signalName) const
{
    for (KWQSignal *signal = _signalListHead; signal; signal = signal->_next) {
        if (KWQNamesMatch(signalName, signal->_name)) {
            return signal;
        }
    }
    return 0;
}

void QObject::connect(const QObject *sender, const char *signalName, const QObject *receiver, const char *member)
{
    // FIXME: Assert that sender is not NULL rather than doing the if statement.
    if (!sender) {
        return;
    }
    
    KWQSignal *signal = sender->findSignal(signalName);
    if (!signal) {
#if !ERROR_DISABLED
        if (1
            && !KWQNamesMatch(member, SIGNAL(setStatusBarText(const QString &)))
            && !KWQNamesMatch(member, SLOT(slotHistoryChanged()))
            && !KWQNamesMatch(member, SLOT(slotJobPercent(KIO::Job *, unsigned long)))
            && !KWQNamesMatch(member, SLOT(slotJobSpeed(KIO::Job *, unsigned long)))
            && !KWQNamesMatch(member, SLOT(slotScrollBarMoved()))
            && !KWQNamesMatch(member, SLOT(slotShowDocument(const QString &, const QString &)))
            && !KWQNamesMatch(member, SLOT(slotViewCleared())) // FIXME: Should implement this one!
            )
	ERROR("connecting member %s to signal %s, but that signal was not found", member, signalName);
#endif
        return;
    }
    signal->connect(KWQSlot(const_cast<QObject *>(receiver), member));
}

void QObject::disconnect(const QObject *sender, const char *signalName, const QObject *receiver, const char *member)
{
    // FIXME: Assert that sender is not NULL rather than doing the if statement.
    if (!sender)
        return;
    
    KWQSignal *signal = sender->findSignal(signalName);
    if (!signal) {
        // FIXME: ERROR
        return;
    }
    signal->disconnect(KWQSlot(const_cast<QObject *>(receiver), member));
}

KWQObjectSenderScope::KWQObjectSenderScope(const QObject *o)
    : _savedSender(QObject::_sender)
{
    QObject::_sender = o;
}

KWQObjectSenderScope::~KWQObjectSenderScope()
{
    QObject::_sender = _savedSender;
}

QObject::QObject(QObject *parent, const char *name)
    : _signalListHead(0), _signalsBlocked(false)
    , _destroyed(this, SIGNAL(destroyed()))
    , _eventFilterObject(0)
{
    _guardedPtrDummyList.append(this);
}

QObject::~QObject()
{
    _destroyed.call();
    ASSERT(_signalListHead == &_destroyed);
    killTimers();
}

void QObject::timerEvent(QTimerEvent *te)
{
}

bool QObject::event(QEvent *)
{
    return false;
}

void QObject::pauseTimer (int _timerId, const void *key)
{
    NSMutableDictionary *timers = (NSMutableDictionary *)CFDictionaryGetValue(timerDictionaries, this);
    NSNumber *timerId = [NSNumber numberWithInt:_timerId];
    NSTimer *timer = (NSTimer *)[timers objectForKey:timerId];

    if ([timer isValid]){
        KWQObjectTimerTarget *target = (KWQObjectTimerTarget *)[timer userInfo];

        if (target){
            NSDate *fireDate = [timer fireDate];
            NSTimeInterval remainingTime = [fireDate timeIntervalSinceDate: [NSDate date]];
        
            if (remainingTime < 0)
                remainingTime = DBL_EPSILON;
            
            if (allPausedTimers == NULL) {
                // The global targets dictionary itself leaks, but the contents are removed
                // when each timer fires or is killed.
                allPausedTimers = CFDictionaryCreateMutable(NULL, 0, NULL, &kCFTypeDictionaryValueCallBacks);
            }
    
            NSMutableArray *pausedTimers = (NSMutableArray *)CFDictionaryGetValue(allPausedTimers, key);
            if (pausedTimers == nil) {
                pausedTimers = [[NSMutableArray alloc] init];
                CFDictionarySetValue(allPausedTimers, key, pausedTimers);
                [pausedTimers release];
            }
            
            target->remainingTime = remainingTime;
            [pausedTimers addObject:target];
                    
            [timer invalidate];
            [timers removeObjectForKey:timerId];
        }
    }
}

void QObject::_addTimer(NSTimer *timer, int _timerId)
{
    NSMutableDictionary *timers = (NSMutableDictionary *)CFDictionaryGetValue(timerDictionaries, this);
    if (timers == nil) {
        timers = [[NSMutableDictionary alloc] init];
        CFDictionarySetValue(timerDictionaries, this, timers);
        [timers release];
    }
    [timers setObject:timer forKey:[NSNumber numberWithInt:_timerId]];
}

static int nextTimerID = 1;

void QObject::clearPausedTimers (const void *key)
{
    if (allPausedTimers)
        CFDictionaryRemoveValue(allPausedTimers, key);
}

void QObject::resumeTimers (const void *key, QObject *_target)
{
    if (allPausedTimers == NULL) {
        return;
    }
    
    int maxId = MAX(0, nextTimerID);
        
    NSMutableArray *pausedTimers = (NSMutableArray *)CFDictionaryGetValue(allPausedTimers, key);
    if (pausedTimers == nil)
        return;

    int count = [pausedTimers count];
    while (count--){
        KWQObjectTimerTarget *target = [pausedTimers objectAtIndex: count];
        target->target = _target;
        NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:target->remainingTime
            target:target
            selector:@selector(timerFired)
            userInfo:target
            repeats:YES];
        [pausedTimers removeLastObject];

        maxId = MAX (maxId, target->timerId);
                        
        _addTimer (timer, target->timerId);
    }
    nextTimerID = maxId+1;
    
    CFDictionaryRemoveValue(allPausedTimers, key);
}

int QObject::startTimer(int milliseconds)
{
    if (timerDictionaries == NULL) {
        // The global timers dictionary itself lives forever, but the contents are removed
        // when each timer fires or is killed.
        timerDictionaries = CFDictionaryCreateMutable(NULL, 0, NULL, &kCFTypeDictionaryValueCallBacks);
    }
    
    NSMutableDictionary *timers = (NSMutableDictionary *)CFDictionaryGetValue(timerDictionaries, this);
    if (timers == nil) {
        timers = [[NSMutableDictionary alloc] init];
        CFDictionarySetValue(timerDictionaries, this, timers);
        [timers release];
    }

    KWQObjectTimerTarget *target = [[KWQObjectTimerTarget alloc] initWithQObject:this timerId:nextTimerID];
    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:milliseconds / 1000.0
          target:target
        selector:@selector(timerFired)
        userInfo:target
         repeats:YES];
    [target release];
    
    _addTimer (timer, nextTimerID);
    
    return nextTimerID++;    
}

void QObject::killTimer(int _timerId)
{
    if (_timerId == 0) {
        return;
    }
    if (timerDictionaries == NULL) {
        return;
    }
    NSMutableDictionary *timers = (NSMutableDictionary *)CFDictionaryGetValue(timerDictionaries, this);
    NSNumber *timerId = [NSNumber numberWithInt:_timerId];
    NSTimer *timer = (NSTimer *)[timers objectForKey:timerId];
    // Only try to remove the timer is it hasn't fired (and is therefore valid).  It is NOT
    // permissible to reference a timer's userInfo if it is invalid.
    if ([timer isValid]){
        [deferredTimers removeObject:(KWQObjectTimerTarget *)[timer userInfo]];
        [timer invalidate];
    }
    [timers removeObjectForKey:timerId];
}

void QObject::killTimers()
{
    if (timerDictionaries == NULL) {
        return;
    }
    NSMutableDictionary *timers = (NSMutableDictionary *)CFDictionaryGetValue(timerDictionaries, this);
    if (timers == nil) {
        return;
    }
    NSEnumerator *e = [timers keyEnumerator];
    NSNumber *timerId;
    while ((timerId = [e nextObject]) != nil) {
	killTimer([timerId intValue]);
    }

    CFDictionaryRemoveValue(timerDictionaries, this);
}

void QObject::setDefersTimers(bool defers)
{
    if (defers) {
        _defersTimers = true;
        deferringTimers = true;
        [NSObject cancelPreviousPerformRequestsWithTarget:[KWQObjectTimerTarget class]];
        return;
    }
    
    if (_defersTimers) {
        _defersTimers = false;
        if (deferringTimers) {
            [KWQObjectTimerTarget performSelector:@selector(stopDeferringTimers) withObject:nil afterDelay:0];
        }
    }
}

@implementation KWQObjectTimerTarget

- initWithQObject:(QObject *)qo timerId:(int)t
{
    [super init];
    target = qo;
    timerId = t;
    return self;
}

- (void)sendTimerEvent
{
    QTimerEvent event(timerId);
    target->timerEvent(&event);
}

- (void)timerFired
{
    if (deferringTimers) {
        if (deferredTimers == nil) {
            deferredTimers = [[NSMutableArray alloc] init];
        }
	if (![deferredTimers containsObject:self]) {
	    [deferredTimers addObject:self];
	}
    } else {
        [self sendTimerEvent];
    }
}

+ (void)stopDeferringTimers
{
    ASSERT(deferringTimers);
    while ([deferredTimers count] != 0) {
	// remove before sending the timer event, in case the timer
	// callback cancels the timer - we don't want to remove too
	// much in that case.
	KWQObjectTimerTarget *timerTarget = [deferredTimers objectAtIndex:0];
	[timerTarget retain];
	[deferredTimers removeObjectAtIndex:0];
        [timerTarget sendTimerEvent];
	[timerTarget release];
    }

    deferringTimers = false;
}

@end

bool QObject::inherits(const char *className) const
{
    if (strcmp(className, "KHTMLPart") == 0) {
        return isKHTMLPart();
    }
    if (strcmp(className, "KHTMLView") == 0) {
        return isKHTMLView();
    }
    if (strcmp(className, "KParts::Factory") == 0) {
        return false;
    }
    if (strcmp(className, "KParts::ReadOnlyPart") == 0) {
        return isKPartsReadOnlyPart();
    }
    if (strcmp(className, "QFrame") == 0) {
        return isQFrame();
    }
    if (strcmp(className, "QScrollView") == 0) {
        return isQScrollView();
    }
    ERROR("class name %s not recognized", className);
    return false;
}

bool QObject::isKHTMLPart() const
{
    return false;
}

bool QObject::isKHTMLView() const
{
    return false;
}

bool QObject::isKPartsReadOnlyPart() const
{
    return false;
}

bool QObject::isQFrame() const
{
    return false;
}

bool QObject::isQScrollView() const
{
    return false;
}
