//
// © 2018-2020 Qualcomm Technologies, Inc. and/or its subsidiaries. All rights reserved.
//

#import <UIKit/UIKit.h>

#import "IoTIotivity.h"
#import "IoTIotivityDispatcher.h"
#import "IoTRepresentationUtils.h"
#import "IoTServiceDiscoveryResult.h"

#include <OCRepresentation.h>
#include <OCResource.h>
#include <OCApi.h>
#include <OCPlatform.h>

@interface IoTIotivity()

@property (atomic, assign) BOOL started;

@end

@implementation IoTIotivity

- (instancetype)init
{
    self = [super init];
    if (self) {
        [self configure];
        [self start];
    }
    return self;
}

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

- (void)configure
{
    OC::PlatformConfig cfg {
        OC::ServiceType::InProc,
        OC::ModeType::Client,
        nullptr
    };
    cfg.transportType = static_cast<OCTransportAdapter>(OCTransportAdapter::OC_ADAPTER_IP);
    cfg.QoS = OC::QualityOfService::HighQos;

    OC::OCPlatform::Configure(cfg);
}

- (void)start
{
    if (self.started) return;
    NSLog(@"Starting Iotivity");
    self.started = YES;
    OC::OCPlatform::start();
}

- (void)stop
{
    if (!self.started) return;
    NSLog(@"Stopping Iotivity");
    self.started = NO;
    OC::OCPlatform::stop();
}

- (void)scanForServicesOfTypes:(NSSet<NSString *> *)types
             serviceDiscovered:(IoTTransportServiceDiscoveredCallback)serviceDiscovered
                         queue:(dispatch_queue_t)serviceDiscoveredQueue
{
    if (!self.started) {
        [self start];
    }
    std::string discovery_uri = OC_MULTICAST_PREFIX;
    discovery_uri += OC_RSRVD_DEVICE_URI;
    OCConnectivityType conn_type = CT_ADAPTER_IP;

    OC::OCPlatform::getDeviceInfo("",
                                  discovery_uri,
                                  conn_type,
                                  [types,serviceDiscovered,serviceDiscoveredQueue]
                                  (const OC::OCRepresentation& rep) -> void {
                                      BOOL isRequestedDevice = NO;
                                      NSString *devType = nil;
                                      for (const auto& cppDevType : rep.getResourceTypes()) {
                                          devType = [NSString stringWithUTF8String:cppDevType.c_str()];
                                          isRequestedDevice = ([types member:devType] != nil);
                                          if (isRequestedDevice) {
                                              // Device matching to user requested type
                                              std::string cppHost = rep.getHost();
                                              IoTServiceDiscoveryResult *device = [IoTServiceDiscoveryResult
                                                                                   resultWithIdentifier:stringValueFromRepresentation(rep, OC_RSRVD_DEVICE_ID)
                                                                                   name:stringValueFromRepresentation(rep, OC_RSRVD_DEVICE_NAME)
                                                                                   host:[NSString stringWithUTF8String:cppHost.c_str()]
                                                                                   type:devType];
                                              dispatch_async(serviceDiscoveredQueue, ^{
                                                  serviceDiscovered(device);
                                              });
                                          }
                                      }
                                  });
}

- (void)scanForResourcesForHost:(NSString *)host
                      uriPrefix:(NSString *)uriPrefix
             resourceDiscovered:(IoTTransportResourceDiscoveredCallback)resourceDiscovered
                          queue:(dispatch_queue_t)resourceDiscoveredQueue
{
    if (!self.started) {
        [self start];
    }
    std::ostringstream discovery_request;
    OCConnectivityType conn_type = CT_ADAPTER_IP;
    discovery_request << OC_RSRVD_WELL_KNOWN_URI;

    std::string cppHost([host UTF8String]);
    OCStackResult result = OC::OCPlatform::findResource(cppHost,
                                                        discovery_request.str(),
                                                        conn_type,
                                                        [uriPrefix,resourceDiscovered,resourceDiscoveredQueue]
                                                        (std::shared_ptr<OC::OCResource> resource) -> void {
                                                            if (resource) {
                                                                std::string cppResUri = resource->uri();
                                                                NSString *resUri = [NSString stringWithUTF8String:cppResUri.c_str()];
                                                                if ([uriPrefix length] > 0 &&
                                                                    [resUri length] > 0 &&
                                                                    [resUri hasPrefix:uriPrefix]) {
                                                                    std::vector<std::string> res_types = resource->getResourceTypes();

                                                                    for (const auto& cppResType : res_types) {
                                                                        NSString *resType = [NSString stringWithUTF8String:cppResType.c_str()];
                                                                        dispatch_async(resourceDiscoveredQueue, ^{
                                                                            resourceDiscovered(resType);
                                                                        });
                                                                    }
                                                                }
                                                            }
                                                        });
    if (OC_STACK_OK != result) {
        NSLog(@"**** Resource scan start failed");
    }
}

- (id<IoTTransportDispatcher>)dispatcher
{
    return [[IoTIotivityDispatcher alloc] init];
}

@end
