/*
 * Copyright (C) 2011 by Matthias Ringwald
 *
 * 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.
 * 3. Neither the name of the copyright holders nor the names of
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY MATTHIAS RINGWALD AND CONTRIBUTORS
 * ``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 MATTHIAS
 * RINGWALD 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 "BluetoothController.h"

#pragma mark callback handler
static void btstackStoppedCallback(CFNotificationCenterRef  center,
                                   void                    *observer,
                                   CFStringRef              name,
                                   const void              *object,
                                   CFDictionaryRef          userInfo) {
    [[BluetoothController sharedInstance] connectionBroke];
}

static void bt_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t* packet, uint16_t size) {
    if (packet_type != HCI_EVENT_PACKET) return;
    [[BluetoothController sharedInstance] eventPacketHandler:packet withSize:size];
}


static BluetoothController* sharedInstance = nil;

@implementation BluetoothController

@synthesize listener;

+(BluetoothController*) sharedInstance{
    if (!sharedInstance) {
        sharedInstance = [[self alloc] init];
    }
    return sharedInstance;
}

-(void) resetState{
    // initial state
    isConnected = NO;
    system_bluetooth = NO;
    hci_state = HCI_STATE_OFF;
    state = kIdle;
    targetType = BluetoothTypeNone;
}

-(id)init{
    
    self = [super init];
    
    listener = nil;
    
    // register for BTstack restart notficiations
    CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), // the notification center to use
                                    NULL,                      // an arbitrary observer-identifier
                                    btstackStoppedCallback,    // callback to call when a notification is posted
                                    CFSTR("ch.ringwald.btstack.stopped"), // notification name
                                    NULL,              // optional object identifier to filter notifications
                                    CFNotificationSuspensionBehaviorDrop); // suspension behavior
    
    // set up libBTstack
    run_loop_init(RUN_LOOP_COCOA);
    bt_register_packet_handler(bt_packet_handler);
    
    return self;
}

-(BOOL)open{

    if (!isConnected) {

        [self resetState];
        
        int err = bt_open();
        if (err){
            NSLog(@"Cannot connect to BTdaemon!");
            return NO;
        }
        // NSLog(@"Connected to BTdaemon!");
        isConnected = YES;
    }

    // get status
    bt_send_cmd(&btstack_get_state);

    return YES;
}

-(void)connectionBroke {
    // NSLog(@"BTstack stopped");
    [self resetState];
    [self open];
    [listener bluetoothStateChanged];
}

-(void)close{
    if (isConnected) {
        // NSLog(@"Disconnected from BTdaemon!");
        bt_close();
    }
    [self resetState];
}

-(BOOL)isConnected{
    return isConnected;
}

-(BluetoothType_t)targetType{
    if (isActive) {
        return targetType;
    }
    return [self bluetoothType];
}

-(BluetoothType_t) bluetoothType {
    if (hci_state != HCI_STATE_OFF) {
        return BluetoothTypeBTstack;
    }
    if (system_bluetooth) {
        return BluetoothTypeApple;
    }
    return BluetoothTypeNone;
}

-(BOOL)isActive{
    return isActive;
}

-(BOOL)canChange{
    if (isActive)     return NO;
    if (!isConnected) return NO;
    return YES;
}


-(void)bluetoothStateChanged {
    
    BluetoothType_t type = [self bluetoothType];
    
    if (state != kIdle && type != targetType) {
        return;
    }
    
    // NSLog(@"bluetoothStateChanged %u", type);
    
    state = kIdle;
    
    isActive = NO;
    [listener bluetoothStateChanged];
}

-(void)eventPacketHandler:(uint8_t*) packet withSize:(uint16_t) size {
    // NSLog(@"bt_packet_handler event: %u, state %u", packet[0], state);
    
    // update state
    switch(packet[0]){
        case BTSTACK_EVENT_STATE:
            hci_state = packet[2];
            // NSLog(@"new BTSTACK_EVENT_STATE %u", hci_state);
            break;
        case BTSTACK_EVENT_SYSTEM_BLUETOOTH_ENABLED:
            system_bluetooth = packet[2];
            // NSLog(@"new BTSTACK_EVENT_SYSTEM_BLUETOOTH_ENABLED %u", system_bluetooth);
            break;
        default:
            break;
    }
    
    switch(state){
            
        case kIdle:
            if (packet[0] == BTSTACK_EVENT_STATE) {
                if (hci_state == HCI_STATE_OFF) {
                    bt_send_cmd(&btstack_get_system_bluetooth_enabled);
                } else {
                    system_bluetooth = 0;
                }
            }
            break;
            
        case kW4SystemOffToEnableBTstack:
            if (packet[0] == BTSTACK_EVENT_SYSTEM_BLUETOOTH_ENABLED) {
                if (system_bluetooth == 0){
                    bt_send_cmd(&btstack_set_power_mode, HCI_POWER_ON);
                    state = kIdle;
                }
            }
            break;
            
        case kW4BTstackOffToEnableSystem:
            if (packet[0] == BTSTACK_EVENT_STATE) {
                if (hci_state == HCI_STATE_OFF) {
                    // NSLog(@"Sending set system bluetooth enable A");
                    bt_send_cmd(&btstack_set_system_bluetooth_enabled, 1);
                }
            }
            if (packet[0] == BTSTACK_EVENT_SYSTEM_BLUETOOTH_ENABLED) {
                if (system_bluetooth == 0){
                    // NSLog(@"Sending set system bluetooth enable B");
                    bt_send_cmd(&btstack_set_system_bluetooth_enabled, 1);
                } else {
                    // NSLog(@"Sending set system bluetooth enable DONE");
                    state = kIdle;
                }
            }
            break;
            
        case kW4Transition:
            break;
    }
    
    [self bluetoothStateChanged];
    
}

-(void)requestType:(BluetoothType_t)bluetoothType{
    // NSLog(@"bluetoothChangeRequested: old %u, new %u", [self bluetoothType], bluetoothType);

    // ignore taps during transition
    if (state != kIdle) {
        return;
    };

    switch ([self bluetoothType]){
        case BluetoothTypeNone:
            switch (bluetoothType) {
                case BluetoothTypeNone:
                    break;
                case BluetoothTypeApple:
                    state = kW4BTstackOffToEnableSystem;    // hack: turning on iOS after BTstack fails, this will make it retry
                    bt_send_cmd(&btstack_set_system_bluetooth_enabled, 1);
                    targetType = BluetoothTypeApple;
                    isActive = YES;
                    break;
                case BluetoothTypeBTstack:
                    state = kW4Transition;
                    bt_send_cmd(&btstack_set_bluetooth_enabled, 1);
                    targetType = BluetoothTypeBTstack;
                    isActive = YES;
                    break;
            }
            break;
        case BluetoothTypeApple:
            switch (bluetoothType) {
                case BluetoothTypeNone:
                    state = kW4Transition;
                    bt_send_cmd(&btstack_set_system_bluetooth_enabled, 0);
                    targetType = BluetoothTypeNone;
                    isActive = YES;
                    break;
                case BluetoothTypeApple:
                    break;
                case BluetoothTypeBTstack:
                    state = kW4SystemOffToEnableBTstack;
                    bt_send_cmd(&btstack_set_system_bluetooth_enabled, 0);
                    targetType = BluetoothTypeBTstack;
                    isActive = YES;
                    break;
            }
            break;
        case BluetoothTypeBTstack:
            switch (bluetoothType) {
                case BluetoothTypeNone:
                    state = kW4Transition;
                    bt_send_cmd(&btstack_set_bluetooth_enabled, 0);
                    targetType = BluetoothTypeNone;
                    isActive = YES;
                    break;
                case BluetoothTypeApple:
                    state = kW4BTstackOffToEnableSystem;
                    bt_send_cmd(&btstack_set_bluetooth_enabled, 0);
                    targetType = BluetoothTypeApple;
                    isActive = YES;
                    break;
                case BluetoothTypeBTstack:
                    break;
            }
            break;
    }
}


@end