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

#import "IoTIotivityDispatcher.h"
#import "IoTGlobals.h"
#import "IoTWrappedRepresentation.h"
#import "IoTBaseResourceClient.h"

//#include <stdexcept>
#include <OCApi.h>
#include <OCPlatform.h>

@interface IoTIotivityDispatcher ()

@property (nonatomic, copy) NSString * uri;
@property (nonatomic, copy) NSString * resourceType;
@property (nonatomic, strong) dispatch_queue_t dispatchQueue;

@end

@implementation IoTIotivityDispatcher {
    std::shared_ptr<OC::OCResource> resource;
}

- (instancetype)init
{
    self = [super init];
    if (self) {
        self->_dispatchQueue = dispatch_queue_create("com.qti.qualcomm.IoTIotivityDispatcher.dispatchqueue", DISPATCH_QUEUE_SERIAL);
    }
    return self;
}

- (void)configureForHost:(NSString *)host
                     uri:(NSString *)uri
            resourceType:(NSString *)resourceType
              interfaces:(NSArray<NSString *> *)interfaces
              observable:(BOOL)observable
{
    self.uri = uri;
    self.resourceType = resourceType;

    makeWeak(self)
    dispatch_async(self.dispatchQueue, ^{
        makeStrongAndReturnIfNil(self)
        std::vector<std::string> vec;
        for (NSString *str in interfaces) {
            vec.emplace_back(std::string([str UTF8String]));
        }

        self->resource = OC::OCPlatform::constructResourceObject([host UTF8String],
                                                                 [uri UTF8String],
                                                                 CT_ADAPTER_IP,
                                                                 observable,
                                                                 { [resourceType UTF8String] },
                                                                 vec);
    });
}

- (BOOL)getWithQueryParameters:(IoTQueryParams)queryParams
                    completion:(IoTTransportDispatcherCompletion)completion
               completionQueue:(dispatch_queue_t)completionQueue
{
    __block OCStackResult result;
    dispatch_sync(self.dispatchQueue, ^{
        OC::QueryParamsMap cppParams = [self queryParamsFromDictionary:queryParams];

        result = self->resource->get(cppParams,
                                     [completionQueue,completion]
                                     (const OC::HeaderOptions&,
                                      const OC::OCRepresentation& rep,
                                      const int code) -> void {
                                         IoTWrappedRepresentation *wrappedRep = [IoTWrappedRepresentation wrapperWithRepresentation:rep];
                                         dispatch_async(completionQueue, ^{
                                             completion(wrappedRep, code);
                                         });
                                     });
    });
    return result == OC_STACK_OK;
}

- (BOOL)postAttributes:(IoTWrappedRepresentation *)representation
       queryParameters:(IoTQueryParams)queryParams
            completion:(IoTTransportDispatcherCompletion)completion
       completionQueue:(dispatch_queue_t)completionQueue
{
    __block OCStackResult result;
    dispatch_sync(self.dispatchQueue, ^{
        OC::QueryParamsMap cppParams = [self queryParamsFromDictionary:queryParams];

        //    NSLog(@"**** POSTING to %@ with keys: %@", self.resourceType, representation.keys);
        result = self->resource->post(
                                      [representation representation],
                                      cppParams,
                                      [self,completionQueue,completion]
                                      (const OC::HeaderOptions& /*header_options*/,
                                       const OC::OCRepresentation& rep,
                                       const int code) -> void {
                                          //                                              NSLog(@"**** Got POST reply for %@ - dispatching", self.resourceType);
                                          IoTWrappedRepresentation *wrappedRep = [IoTWrappedRepresentation wrapperWithRepresentation:rep];
                                          dispatch_async(completionQueue, ^{
                                              completion(wrappedRep, code);
                                          });
                                      });
    });
    return result == OC_STACK_OK;
}

- (BOOL)startObservingWithQueryParameters:(IoTQueryParams)params
                               didObserve:(IoTTransportDispatcherObserveCompletion)didObserve
                             observeQueue:(dispatch_queue_t)observeQueue
{
    __block OCStackResult result;
    dispatch_sync(self.dispatchQueue, ^{
        OC::QueryParamsMap cppParams = [self queryParamsFromDictionary:params];

        result = self->resource->observe(
                                         OC::ObserveType::ObserveAll,
                                         cppParams,
                                         [observeQueue,didObserve]
                                         (const OC::HeaderOptions& /*header_options*/,
                                          const OC::OCRepresentation& rep,
                                          const int code,
                                          const int& seq_num) -> void {
                                             IoTWrappedRepresentation *wrappedRep = [IoTWrappedRepresentation wrapperWithRepresentation:rep];
                                             dispatch_async(observeQueue, ^{
                                                 didObserve(wrappedRep, code, 0/*seq_num*/);
                                             });
                                         });
    });
    return result == OC_STACK_OK;
}

- (void)stopObserving
{
    dispatch_sync(self.dispatchQueue, ^{
        try {
            self->resource->cancelObserve();
        } catch (...) {
            // Sometimes we get an exception here.
            NSLog (@"CAUGHT EXCEPTION");
        }
    });
}

- (OC::QueryParamsMap)queryParamsFromDictionary:(IoTQueryParams)dict
{
    OC::QueryParamsMap params;
    for (NSString *key in [dict allKeys]) {
        params[[key UTF8String]] = [dict[key] UTF8String];
    }
    return params;
}

@end
