//
//  UDPController.m
//  SFS2X
//
//  Created by Wayne Helman on 12-01-20.
//  Copyright 2012 A51 Integrated | http://a51integrated.com. All rights reserved.
//

#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <fcntl.h>
#include <netdb.h>

#import "UDPController.h"


@interface UDPController ()

@property (nonatomic, copy,   readwrite) NSString *hostName;
@property (nonatomic, copy,   readwrite) NSData *hostAddress;
@property (nonatomic, readwrite) NSUInteger port;

- (void)_stopHostResolution;
- (void)_stopWithError:(NSError *)error;
- (void)_stopWithStreamError:(CFStreamError)streamError;

@end

@implementation UDPController

@synthesize delegate    = _delegate;
@synthesize hostName    = _hostName;
@synthesize hostAddress = _hostAddress;
@synthesize port        = _port;


- (id)init
{
    self = [super init];
    if (self != nil)
    {
        // do nothing
    }
    return self;
}

-(void)dealloc
{
    [self stop];
}


- (void)_sendData:(NSData *)data
{
    int                     err;
    int                     sock;
    ssize_t                 bytesWritten;
    const struct sockaddr * addrPtr = NULL;
    socklen_t               addrLen = 0;
    
    sock = CFSocketGetNative(_cfSocket);
    
    bytesWritten = sendto(sock, [data bytes], [data length], 0, addrPtr, addrLen);
    
    if (bytesWritten < 0)
    {
        err = errno;
    }
    else  if (bytesWritten == 0)
    {
        err = EPIPE;                    
    }
    else
    {
        err = 0;
    }
    
    if (err != 0)
    {
        if ((self.delegate != nil) && [self.delegate respondsToSelector:@selector(udpc:didFailToSendData:toAddress:error:)])
        {
            [self.delegate udpc:self didFailToSendData:data toAddress:self.hostAddress error:[NSError errorWithDomain:NSPOSIXErrorDomain code:err userInfo:nil]];
        }
    }
}

- (void)_readData
{
    int                     err;
    int                     sock;
    struct sockaddr_storage addr;
    socklen_t               addrLen;
    uint8_t                 buffer[65536];
    ssize_t                 bytesRead;
    
    sock = CFSocketGetNative(_cfSocket);
    assert(sock >= 0);
    
    addrLen = sizeof(addr);
    bytesRead = recvfrom(sock, buffer, sizeof(buffer), 0, (struct sockaddr *) &addr, &addrLen);
    
    // WH. Changed from 0 to 4 (2013-03-05)
    //Check for existence of header bytes and to see if packet is larger than
    //buffer.

    if (bytesRead < 4)
    {
        err = errno;
    }
    else if (bytesRead == sizeof(buffer))
    {
        err = EINVAL;
        // warn("datagram too large for buffer: truncated");
    }
    else
    {
        NSData *    dataObj;
        NSData *    addrObj;
        
        err = 0;
        
        //NSLog(@"UDP bytesRead %ld", (long)bytesRead);
        
        dataObj = [NSData dataWithBytes:buffer length:bytesRead];
        addrObj = [NSData dataWithBytes:&addr  length:addrLen  ];
        
        if ( (self.delegate != nil) && [self.delegate respondsToSelector:@selector(udpc:didReceiveData:fromAddress:)] )
        {
            [self.delegate udpc:self didReceiveData:dataObj fromAddress:addrObj];
        }
    }
    
    if (err != 0)
    {
        if ( (self.delegate != nil) && [self.delegate respondsToSelector:@selector(udpc:didReceiveError:)] )
        {
            [self.delegate udpc:self didReceiveError:[NSError errorWithDomain:NSPOSIXErrorDomain code:err userInfo:nil]];
        }
    }
}

static void SocketReadCallback(CFSocketRef s, CFSocketCallBackType type, CFDataRef address, const void *data, void *info)
{
    if (type == kCFSocketDataCallBack)
    {
        NSData *nsData = (__bridge NSData *)data;
        if (nsData.length == 0)
        {
            NSLog(@"ERROR: Received kCFSocketDataCallBack, but with 0 bytes. UDP connection terminated");
        }
    }
    
    UDPController *obj;
    obj = (__bridge UDPController *) info;
    [obj _readData];

}


- (BOOL)_setupSocketConnectedToAddress:(NSData *)address port:(NSUInteger)port error:(NSError **)errorPtr
{
    int                     err;
    int                     sock;
    const CFSocketContext   context = { 0, (__bridge void *)(self), NULL, NULL, NULL };
    CFRunLoopSourceRef      rls;
    
    err = 0;
    sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock < 0)
    {
        err = errno;
    }
     
    if (err == 0)
    {
        struct sockaddr_in      addr;
        
        memset(&addr, 0, sizeof(addr));

        if ([address length] > sizeof(addr))
        {
            [address getBytes:&addr length:sizeof(addr)];
        }
        else
        {
            [address getBytes:&addr length:[address length]];
        }

        addr.sin_port = htons(port);
        err = connect(sock, (const struct sockaddr *) &addr, sizeof(addr));

        if (err < 0)
        {
            err = errno;
        }
    }
    
    if (err == 0)
    {
        int flags;
        
        flags = fcntl(sock, F_GETFL);
        err = fcntl(sock, F_SETFL, flags | O_NONBLOCK);
        if (err < 0)
        {
            err = errno;
        }
    }
    
    if (err == 0)
    {
        _cfSocket = CFSocketCreateWithNative(NULL, sock, kCFSocketReadCallBack, SocketReadCallback, &context);
        sock = -1;
        rls = CFSocketCreateRunLoopSource(NULL, _cfSocket, 0);
        CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode);
        CFRelease(rls);
    }
    
    if (sock != -1)
    {
        close(sock);
        NSLog(@"UDP initialization error: unable to connect socket");
    }
    if ((_cfSocket == NULL) && (errorPtr != NULL))
    {
        *errorPtr = [NSError errorWithDomain:NSPOSIXErrorDomain code:err userInfo:nil];
    }

    return (err == 0);
}


- (void)_hostResolutionDone
{
    NSError *error;
    Boolean resolved;
    NSArray *resolvedAddresses;
    
    error = nil;
    
    resolvedAddresses = (__bridge NSArray *) CFHostGetAddressing(_cfHost, &resolved);
    
    if (resolved && (resolvedAddresses != nil))
    {
        for (NSData * address in resolvedAddresses)
        {
            BOOL                    success;
            const struct sockaddr * addrPtr;
            
            addrPtr = (const struct sockaddr *) [address bytes];
            
            success = NO;
            if (addrPtr->sa_family == AF_INET)
            {
                success = [self _setupSocketConnectedToAddress:address port:self.port error:&error];

                if (success)
                {
                    CFDataRef hostAddress;
                    hostAddress = CFSocketCopyPeerAddress(_cfSocket);
                    self.hostAddress = (__bridge NSData *) hostAddress;
                    CFRelease(hostAddress);
                }
            }
            if (success)
            {
                break;
            }
        }
    }
    
    if ((self.hostAddress == nil) && (error == nil))
    {
        error = [NSError errorWithDomain:(NSString *)kCFErrorDomainCFNetwork code:kCFHostErrorHostNotFound userInfo:nil];
    }
    
    if (error == nil)
    {        
        [self _stopHostResolution];

        if ((self.delegate != nil) && [self.delegate respondsToSelector:@selector(udpc:didStartWithAddress:)])
        {
            [self.delegate udpc:self didStartWithAddress:self.hostAddress];
        }
    }
    else
    {
        [self _stopWithError:error];
    }
}

static void HostResolveCallback(CFHostRef theHost, CFHostInfoType typeInfo, const CFStreamError *error, void *info)
{
    UDPController *obj = (__bridge UDPController *) info;
    
    if ((error != NULL) && (error->domain != 0))
    {
        [obj _stopWithStreamError:*error];
    }
    else
    {
        [obj _hostResolutionDone];
    }
}

- (void)startConnectionToHostName:(NSString *)hostName port:(NSUInteger)port
{
    if (self.port == 0)
    {
        Boolean             success;
        CFHostClientContext context = {0, (__bridge void *)(self), NULL, NULL, NULL};
        CFStreamError       streamError;
        
        _cfHost = CFHostCreateWithName(NULL, (__bridge CFStringRef) hostName);
        
        CFHostSetClient(_cfHost, HostResolveCallback, &context);
        CFHostScheduleWithRunLoop(_cfHost, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
        
        success = CFHostStartInfoResolution(_cfHost, kCFHostAddresses, &streamError);
        
        if (success)
        {
            self.hostName = hostName;
            self.port = port;
        }
        else
        {
            [self _stopWithStreamError:streamError];
        }
    }
}

- (void)sendData:(NSData *)data
{
    if(_cfSocket != nil && CFSocketIsValid(_cfSocket))
    {
       [self _sendData:data];
    }
    else
    {
        NSLog(@"UDP Socket Error: invalid socket");
    }
}

- (void)_stopHostResolution
{
    if (_cfHost != NULL)
    {
        CFHostSetClient(_cfHost, NULL, NULL);
        CFHostCancelInfoResolution(_cfHost, kCFHostAddresses);
        CFHostUnscheduleFromRunLoop(_cfHost, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
        CFRelease(_cfHost);
        _cfHost = NULL;
    }
}

- (void)stop
{
    self.hostName = nil;
    self.hostAddress = nil;
    self.port = 0;
    [self _stopHostResolution];
    if (_cfSocket != NULL && CFSocketIsValid(_cfSocket))
    {
        CFSocketInvalidate(_cfSocket);
        CFRelease(_cfSocket);
        _cfSocket = NULL;
    }
}

- (void)_stopWithError:(NSError *)error
{
    [self stop];
    if ((self.delegate != nil) && [self.delegate respondsToSelector:@selector(udpc:didStopWithError:)])
    {
        [self.delegate udpc:self didStopWithError:error];
    }
}

- (void)_stopWithStreamError:(CFStreamError)streamError
{
    NSDictionary *userInfo;
    NSError *error;
    
    if (streamError.domain == kCFStreamErrorDomainNetDB)
    {
        userInfo = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInteger:streamError.error], kCFGetAddrInfoFailureKey, nil];
    }
    else
    {
        userInfo = nil;
    }
    error = [NSError errorWithDomain:(NSString *)kCFErrorDomainCFNetwork code:kCFHostErrorUnknown userInfo:userInfo];

    [self _stopWithError:error];
}



@end
