#ifndef converters_h
#define converters_h

#include <map>
#include <clc/clcString.h>
#include <clc/clcUri.h>
#include <clc/clcUUID.h>
#include <asl/Typedefs.h>
#include <asl/Sample.h>
#include "asl_wrappers_p.h"

inline NSString *toObjC(const clc::String &s) {
    return [NSString stringWithCString:s.c_str() encoding:NSUTF8StringEncoding];
}

inline clc::String toCpp(NSString *s) {
    return clc::String([s cStringUsingEncoding:NSUTF8StringEncoding]);
}

inline NSURL *toObjC(clc::URI u) {
    return [NSURL URLWithString:toObjC(u.toRawString())];
}

inline clc::URI toCpp(NSURL *u) {
    return clc::URI::fromEncodedString(toCpp([u absoluteString]));
}

inline asl::SessionType toCpp(SessionType t) {
    return asl::SessionType(t);
}

inline OutputType toObjC(asl::OutputType t) {
    return OutputType(t);
}

inline asl::OutputType toCpp(OutputType t) {
    return asl::OutputType(t);
}

inline InitSource toObjC(DrmInterfaceWrapper::InitSource t) {
    return InitSource(t);
}

inline DrmInterfaceWrapper::InitSource toCpp(InitSource t) {
    return DrmInterfaceWrapper::InitSource(t);
}

inline Result toObjC(clc::Result r) {
    return Result(r);
}

inline MessageType toObjC(asl::MessageType type) {
    return MessageType(type);
}

inline NSUUID *toObjC(const clc::Uuid &u) {
    if (u.isNil()) {
        return NULL;
    }
    return [[NSUUID alloc] initWithUUIDBytes:&u[0]];
}

inline NSArray<NSData*> *toObjC(const std::vector<clc::ByteArray> &v) {
    NSMutableArray* array = [NSMutableArray arrayWithCapacity:v.size()];
    for (const auto& bytes : v) {
        [array addObject:[NSData dataWithBytes:bytes.data() length:bytes.size()]];
    }
    return array;
}

inline PayloadType toObjC(asl::PayloadInfo::PayloadType r) {
    return PayloadType(r);
}

inline PeriodBufferReason toObjC(asl::PeriodBufferReason r) {
    return PeriodBufferReason(r);
}

inline SubtitleType toObjC(asl::SubtitleTypeFlag f) {
    return SubtitleType(f);
}

inline TransitionType toObjC(asl::TransitionInfo::TransitionType t) {
    return TransitionType(t);
}

inline AudioFraming toObjC(asl::AudioFormat::Framing f) {
    return AudioFraming(f);
}

inline RequestTag toObjC(asl::RequestTag t) {
    return RequestTag(t);
}

inline AudioQualityInfo *toObjC(const asl::AudioQualityInfo &quality) {
    AudioQualityInfo *objcQuality = [AudioQualityInfo new];
    objcQuality.quality = new asl::AudioQualityInfo(quality);
    return objcQuality;
}

inline VideoQualityInfo *toObjC(const asl::VideoQualityInfo &quality) {
    VideoQualityInfo *objcQuality = [VideoQualityInfo new];
    objcQuality.quality = new asl::VideoQualityInfo(quality);
    return objcQuality;
}

inline PayloadInfo *toObjC(const asl::PayloadInfoRef &payload) {
    PayloadInfo *objcPayload = nil;
    if (payload) {
        switch (payload->payload) {
            case asl::PayloadInfo::SAMPLE:
                objcPayload = [SampleInfo new];
                break;
            case asl::PayloadInfo::SIDECHANNEL:
                objcPayload = [SidechannelInfo new];
                break;
            case asl::PayloadInfo::TRANSITION:
                objcPayload = [TransitionInfo new];
                break;
            case asl::PayloadInfo::TRACK_SELECTION:
                objcPayload = [TrackSelectionInfo new];
                break;
            default:
                objcPayload = [PayloadInfo new];
                break;
        }
        objcPayload.payload = payload;
    }
    return objcPayload;
}

inline AudioTrackInfo *toObjC(const asl::AudioTrackInfo &info) {
    AudioTrackInfo *objcInfo = [AudioTrackInfo new];
    objcInfo.track = new asl::AudioTrackInfo(info);
    return objcInfo;
}
inline VideoTrackInfo *toObjC(const asl::VideoTrackInfo &info) {
    VideoTrackInfo *objcInfo = [VideoTrackInfo new];
    objcInfo.track = new asl::VideoTrackInfo(info);
    return objcInfo;
}
inline SubtitleTrackInfo *toObjC(const asl::SubtitleTrackInfo &info) {
    SubtitleTrackInfo *objcInfo = [SubtitleTrackInfo new];
    objcInfo.track = new asl::SubtitleTrackInfo(info);
    return objcInfo;
}
inline SubtitleSegment *toObjC(const asl::SubtitleSegment &segment) {
    if (segment.duration == clc::Microseconds(0)) {
        return nil;
    }
    SubtitleSegment *objcSegment = [SubtitleSegment new];
    objcSegment.subtitleSegment = new asl::SubtitleSegment(segment);
    return objcSegment;
}
inline SubtitleSegmentFuture *toObjC(std::future<asl::SubtitleSegment> future) {
    SubtitleSegmentFuture *objcFuture = [SubtitleSegmentFuture new];
    objcFuture.future = std::move(future);
    return objcFuture;
}
inline PeriodInfo *toObjC(const asl::PeriodInfo &info) {
    PeriodInfo *objcInfo = [PeriodInfo new];
    objcInfo.info = info;
    return objcInfo;
}
inline DownloadErrorMessage *toObjC(const asl::MessageCallbacks::DownloadErrorMessage &msg) {
    DownloadErrorMessage *objcMessage = [DownloadErrorMessage new];
    objcMessage.message = msg;
    return objcMessage;
}
inline AbrStatMessage *toObjC(const asl::MessageCallbacks::AbrStatMessage &msg) {
    AbrStatMessage *objcMessage = [AbrStatMessage new];
    objcMessage.message = msg;
    return objcMessage;
}
inline PeriodCallbacks *toObjC(const asl::PeriodCallbacks &callbacks) {
    PeriodCallbacks *objcCallbacks = [PeriodCallbacks new];
    objcCallbacks.callbacks = &callbacks;
    return objcCallbacks;
}
inline PSSHEntry *toObjC(const asl::DrmInterface::PSSHEntry &entry) {
    PSSHEntry *objcEntry = [PSSHEntry new];
    objcEntry.entry = entry;
    return objcEntry;
}
inline ProtectionEntry *toObjC(const asl::DrmInterface::ProtectionEntry &entry) {
    ProtectionEntry *objcEntry = [ProtectionEntry new];
    objcEntry.entry = entry;
    return objcEntry;
}
inline AdaptationAlgorithmConfig *toObjC(const asl::AdaptationAlgorithmConfig &config) {
    AdaptationAlgorithmConfig *objc = [AdaptationAlgorithmConfig new];
    objc.minDurationQualityIncreaseMs = config.getConfig().getLong("minDurationQualityIncreaseMs", 10000);
    objc.maxDurationQualityDecreaseMs = config.getConfig().getLong("maxDurationQualityDecreaseMs", 25000);
    objc.bufferDegradationPenalty = config.getConfig().getDouble("bufferDegradationPenalty", 0.2);
    objc.bufferDegradationRecovery = config.getConfig().getDouble("bufferDegradationRecovery", 0.05);
    objc.bufferDegradationSampleSize = config.getConfig().getLong("bufferDegradationSampleSize", 4);
    return objc;
}
inline asl::AdaptationAlgorithmConfig toCpp(AdaptationAlgorithmConfig *t) {
    asl::AdaptationAlgorithmConfig nbaConfig = asl::AdaptationAlgorithmConfig::buildNBAConfig();
    auto params = nbaConfig.getConfig();

    params.put("minDurationQualityIncreaseMs", std::to_string(t.minDurationQualityIncreaseMs).c_str());
    params.put("maxDurationQualityDecreaseMs", std::to_string(t.maxDurationQualityDecreaseMs).c_str());
    params.put("bufferDegradationPenalty", std::to_string(t.bufferDegradationPenalty).c_str());
    params.put("bufferDegradationRecovery", std::to_string(t.bufferDegradationRecovery).c_str());
    params.put("bufferDegradationSampleSize", std::to_string(t.bufferDegradationSampleSize).c_str());
    return asl::AdaptationAlgorithmConfig(nbaConfig.getAlgorithm(), params);
}
inline BandwidthMeterConfig *toObjC(const asl::BandwidthMeterConfig &config) {
    BandwidthMeterConfig *objc = [BandwidthMeterConfig new];
    objc.minSampledBytes = config.getMinSampledBytes();
    objc.defaultBitrateEstimate = config.getDefaultBitrateEstimates();
    objc.estimateFraction = config.getEstimateFraction();
    objc.percentileWeight = config.getPercentileWeight();
    objc.percentile = config.getPercentile();
    objc.alpha = config.getAlpha();
    objc.halfLifeSlow = config.getHalfLifeSlow();
    objc.halfLifeFast = config.getHalfLifeFast();
    return objc;
}
inline asl::BandwidthMeterConfig toCpp(BandwidthMeterConfig *t) {
    asl::BandwidthMeterConfig cfg;
    cfg.setMinSampledBytes(t.minSampledBytes);
    cfg.setDefaultBitrateEstimates(t.defaultBitrateEstimate);
    cfg.setEstimateFraction(t.estimateFraction);
    cfg.setPercentileWeight(t.percentileWeight);
    cfg.setPercentile(t.percentile);
    cfg.setAlpha(t.alpha);
    cfg.setHalfLifeSlow(t.halfLifeSlow);
    cfg.setHalfLifeFast(t.halfLifeFast);
    return cfg;
}

template<typename Item>
inline NSArray<decltype(toObjC(std::vector<Item>().front()))> *toObjC(const std::vector<Item> &vector) {
    NSMutableArray *array = [NSMutableArray arrayWithCapacity:vector.size()];
    for (const auto &item: vector) {
        [array addObject:toObjC(item)];
    }
    return array;
}

inline NSDictionary<NSString*,NSArray<NSString*>*> *toObjC(const std::multimap<std::string,std::string> &map) {
    NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:map.size()];
    for (const auto &item: map) {
        NSString *key = toObjC(item.first);
        if (![dict valueForKey: key]) {
            [dict setValue:[NSMutableArray new] forKey:key];
        }
        NSMutableArray *values = (NSMutableArray*)[dict valueForKey:key];
        [values addObject:toObjC(item.second)];
    }
    return dict;
}

inline NSDictionary<NSString*,NSString*> *toObjC(const std::map<std::string,std::string> &map) {
    NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:map.size()];
    for (const auto &item: map) {
        [dict setObject:toObjC(item.second) forKey:toObjC(item.first)];
    }
    return dict;
}

inline NSArray<ProtectionEntry*> *toObjC(const std::vector<DrmInterfaceWrapper::ProtectionEntry> &v) {
    NSMutableArray* array = [NSMutableArray arrayWithCapacity:v.size()];
    for (const auto& el : v) {
        [array addObject:toObjC(el)];
    }
    return array;
}
#endif // converters_h
