//
//  OSSIPv6Adapter.m
//
//  Created by lingkun on 16/5/16.
//  Copyright © 2016 Ali. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "OSSIPv6Adapter.h"
#import "OSSIPv6PrefixResolver.h"
#import "OSSReachabilityManager.h"
#import "OSSLog.h"

#include <arpa/inet.h>
#include <dns.h>
#include <err.h>
#include <ifaddrs.h>
#include <net/if.h>
#include <netdb.h>
#include <netinet/in.h>
#include <resolv.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/sysctl.h>

#define UNKNOWN_STACK         0
#define SUPPORT_IPV4_STACK    1
#define SUPPORT_IPV6_STACK    2
#define ROUNDUP_LEN(a) \
((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long))
#define TypeEN    "en0"

#define IOS_9_VERSION     @"9.0"

@implementation OSSIPv6Adapter
{
    BOOL isIPv6Only;
    BOOL isIPv6OnlyResolved;
}

- (instancetype)init {
    if (self = [super init]) {
        isIPv6Only = NO;
        isIPv6OnlyResolved = NO;

        // When App switches to active status, refresh the IPv6-only check.
        NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
        [defaultCenter addObserver:self
                          selector:@selector(appDidBecomeActiveFunc)
                              name:UIApplicationDidBecomeActiveNotification
                            object:nil];
    }
    return self;
}

+ (instancetype)getInstance {
    static id singletonInstance = nil;
    static dispatch_once_t once_token;
    dispatch_once(&once_token, ^{
        if (!singletonInstance) {
            singletonInstance = [[super allocWithZone:NULL] init];
        }
    });
    return singletonInstance;
}

- (BOOL)isIPv6OnlyNetwork {
    @synchronized(self) {
        if (isIPv6OnlyResolved) {
            return isIPv6Only;
        }
        
        OSSLogDebug(@"Start resolved network to see if in IPv6-Only env.");
        int localStack = 0;
        
        localStack = SUPPORT_IPV4_STACK | SUPPORT_IPV6_STACK;
        localStack &= [self getDNSServersIpStack];
        
        if (localStack & SUPPORT_IPV4_STACK) {
            // support IPv4
            isIPv6Only = NO;
        } else if (localStack & SUPPORT_IPV6_STACK) {
            // IPv6-Only
            isIPv6Only = YES;
            [[OSSIPv6PrefixResolver getInstance] updateIPv6Prefix];
        } else {
            OSSLogDebug(@"[%s]: Error.", __FUNCTION__);
            isIPv6Only = NO;
        }
        isIPv6OnlyResolved = YES;
        if (isIPv6Only) {
            OSSLogDebug(@"[%s]: IPv6-Only network now.", __FUNCTION__);
        } else {
            OSSLogDebug(@"[%s]: Not IPv6-Only network now.", __FUNCTION__);
        }
        return isIPv6Only;
    }
}

- (void)appDidBecomeActiveFunc {
    OSSLogDebug(@"[%s]: App become active, refresh IPv6-Only status.", __FUNCTION__);
    [self reResolveIPv6OnlyStatus];
}

- (BOOL)reResolveIPv6OnlyStatus {
    isIPv6OnlyResolved = NO;
    return [self isIPv6OnlyNetwork];
}

- (NSString *)handleIpv4Address:(NSString *)addr {
    if (addr == nil || addr.length == 0) {
        return nil;
    }
    
    if ([self isIPv6Address:addr]) return [NSString stringWithFormat:@"[%@]", addr];
    
    NSString *convertedAddr;
    if ([self isIPv6OnlyNetwork]) {
        convertedAddr = [[OSSIPv6PrefixResolver getInstance] convertIPv4toIPv6:addr];
        return [NSString stringWithFormat:@"[%@]", convertedAddr];
    } else  {
        convertedAddr = addr;
    }
    return convertedAddr;
}

/**
 *	@brief	Looks up the DNS server stack and returns the flag combinations of SUPPORT_IPV4_STACK and SUPPORT_IPV6_STACK.
 *
 *	@return
 */
- (int)getDNSServersIpStack {
    int dns_stack = 0;
    res_state res = malloc(sizeof(struct __res_state));
    int result = res_ninit(res);
    if (result == 0) {
        union res_9_sockaddr_union *addr_union = malloc(res->nscount * sizeof(union res_9_sockaddr_union));
        res_getservers(res, addr_union, res->nscount);
        for (int i = 0; i < res->nscount; i++) {
            if (addr_union[i].sin.sin_family == AF_INET) {
                char ip[INET_ADDRSTRLEN];
                if (inet_ntop(AF_INET, &(addr_union[i].sin.sin_addr), ip, INET_ADDRSTRLEN)) {
                    dns_stack |= SUPPORT_IPV4_STACK;
                }
            } else if (addr_union[i].sin6.sin6_family == AF_INET6) {
                char ip[INET6_ADDRSTRLEN];
                if (inet_ntop(AF_INET6, &(addr_union[i].sin6.sin6_addr), ip, INET6_ADDRSTRLEN)) {
                    dns_stack |= SUPPORT_IPV6_STACK;
                }
            } else {
                OSSLogDebug(@"%s: Undefined family.", __FUNCTION__);
            }
        }
        free(addr_union);
    }
    res_ndestroy(res);
    free(res);
    return dns_stack;
}

/**
 *	@brief	Checks if the address is IPV4
 *
 *	@return
 */
- (BOOL)isIPv4Address:(NSString *)addr {
    if (addr == nil) {
        return NO;
    }
    const char *utf8 = [addr UTF8String];
    // Check valid IPv4.
    struct in_addr dst;
    int success = inet_pton(AF_INET, utf8, &(dst.s_addr));
    return success == 1;
}

/**
 *	@brief	Checks if the address is IPV6
 *
 *	@return
 */
- (BOOL)isIPv6Address:(NSString *)addr {
    if (addr == nil) {
        return NO;
    }
    const char *utf8 = [addr UTF8String];
    // Check valid IPv6.
    struct in6_addr dst6;
    int success = inet_pton(AF_INET6, utf8, &dst6);
    return (success == 1);
}

@end