#import "Olm.h"
#import <OLMKit/OLMKit.h>
#import <OLMKit/OLMAccount.h>

@implementation Olm
@synthesize account;
@synthesize inboundGroupSession;
@synthesize utility;
@synthesize session;
@synthesize olmSAS;
@synthesize olmPkSigning;
@synthesize olmPkEncryption;
@synthesize olmPkDecryption;
@synthesize olmOutboundGroupSession;

RCT_EXPORT_MODULE(ReactNativeOlm)

// Example method
// See // https://reactnative.dev/docs/native-modules-ios
//RCT_REMAP_METHOD(multiply,
//                 multiplyWithA:(nonnull NSNumber*)a withB:(nonnull NSNumber*)b
//                 withResolver:(RCTPromiseResolveBlock)resolve
//                 withRejecter:(RCTPromiseRejectBlock)reject)
//{
//  NSNumber *result = @([a floatValue] * [b floatValue]);
//
//  resolve(result);
//}
//
RCT_EXPORT_METHOD(getOlmLibVersion:(NSString *)str
                 resolver:(RCTPromiseResolveBlock)resolve
                 rejecter:(RCTPromiseRejectBlock)reject)
{

    resolve(@"Library Version String");
}

RCT_EXPORT_METHOD(initialize)
{
    NSLog(@"Init Olm");
    OLMKit *kit = [[OLMKit alloc] init];
    [kit init];
}

// =============================================>
// Account
// =============================================>

RCT_EXPORT_METHOD(freeAccount)
{
    NSLog(@"Free Account");
    if (account != nil) {
        [self freeAccount];
    }
}

RCT_EXPORT_METHOD(createAccount)
{
    OLMAccount *acct = [[OLMAccount alloc] init].initNewAccount;
    [self setAccount:acct];
}

RCT_EXPORT_METHOD(identity_keys:
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject)
{
    NSLog(@"Identity Keys");
    if (account != nil) {
        NSDictionary *keys = account.identityKeys;
        resolve([NSString stringWithFormat:@"%@", keys]);
    }
}

RCT_EXPORT_METHOD(signAccount:(NSData *)msg
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject)
{
    NSLog(@"Sign Account");
    if (account != nil) {
        NSString *result = [account signMessage:msg];
        resolve(result);
    }
}

RCT_EXPORT_METHOD(one_time_keys:
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject)
{
    NSLog(@"One Time Keys");
    if (account != nil) {
        NSDictionary *result = [account oneTimeKeys];
        resolve(result);
    }
}

RCT_EXPORT_METHOD(mark_keys_as_published)
{
    NSLog(@"Mark Keys As Published");
    if (account != nil) {
        [account markOneTimeKeysAsPublished];
    }
}

RCT_EXPORT_METHOD(max_number_of_one_time_keys:
                  resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject)
{
    NSLog(@"Mark Keys As Published");
    if (account != nil) {
        NSNumber *maxKeys = @([account maxOneTimeKeys]);
        resolve(maxKeys);
    }
}

RCT_EXPORT_METHOD(generate_one_time_keys:(NSUInteger)number_of_keys)
{
    NSLog(@"Generate one time keys");
    if (account != nil) {
        [account generateOneTimeKeys:number_of_keys];
    }
}

RCT_EXPORT_METHOD(generate_fallback_key)
{
    NSLog(@"Generate Fallback Key");
}

RCT_EXPORT_METHOD(fallback_key)
{
    NSLog(@"Get Fallback Key");
}

RCT_EXPORT_METHOD(pickleAccount:key resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject)
{
    NSLog(@"Pickle Account");
    NSError *error;
    resolve([account serializeDataWithKey:key error:&error]);
}

RCT_EXPORT_METHOD(unpickleAccount:key pickle:pickle resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject)
{
    NSLog(@"Unpickle Account");
    NSError *error;
    resolve([account initWithSerializedData:pickle key:key error:&error]);
}

// =============================================>
// Inbound Group Session
// =============================================>

RCT_EXPORT_METHOD(freeInboundGroupSession)
{
    NSLog(@"Free Inbound Group Session");
    if (inboundGroupSession != nil) {
        [self freeInboundGroupSession];
    }
}

RCT_EXPORT_METHOD(pickleInboundGroupSession:key resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject)
{
    NSLog(@"Pickle Inbound Group Session");
    NSError *error;
    resolve([inboundGroupSession serializeDataWithKey:key error:&error]);
}

RCT_EXPORT_METHOD(unpickleInboundGroupSession :key :(NSString *)pickle resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject)
{
    NSLog(@"Unpickle Inbound Group Session");
    NSError *error;
    resolve([inboundGroupSession initWithSerializedData:pickle key:key error:&error]);
}

RCT_EXPORT_METHOD(createInboundGroupSession:(NSString *)session_key)
{
    NSLog(@"Create Inbound Group Session");
    OLMInboundGroupSession *inboundGroup = [[OLMInboundGroupSession alloc] init].init;
    NSError *error;
    [self setInboundGroupSession:[inboundGroup initInboundGroupSessionWithSessionKey:session_key error:&error]];
}

RCT_EXPORT_METHOD(importInboundGroupSession:(NSString *)session_key)
{
    NSLog(@"Import Inbound Group Session");
    OLMInboundGroupSession *inboundGroup = [[OLMInboundGroupSession alloc] init].init;
    NSError *error;
    [self setInboundGroupSession:[inboundGroup initInboundGroupSessionWithImportedSession:session_key error:&error]];
}

RCT_EXPORT_METHOD(decryptInboundGroupSession:(NSString *)message messageIndex:(NSUInteger*)messageIndex         resolver:(RCTPromiseResolveBlock)resolve
    rejecter:(RCTPromiseRejectBlock)reject)
{
    NSLog(@"Decrypt Inbound Group Session");
    NSError *error;
    resolve([inboundGroupSession decryptMessage:message messageIndex:messageIndex error:&error]);
}

RCT_EXPORT_METHOD(getInboundGroupSessionSessionId: resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
    NSLog(@"Get Inbound Group Session Session ID");
    resolve([inboundGroupSession sessionIdentifier]);
}

RCT_EXPORT_METHOD(getInboundGroupSessionFirstKnownIndex: resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
    NSLog(@"Get Inbound Group Session First Known Index");
    NSNumber *index = @([inboundGroupSession firstKnownIndex]);
    resolve(index);
}

RCT_EXPORT_METHOD(exportInboundGroupSession: resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
    NSLog(@"Export Inbound Group Session");
    NSError *error;
    resolve([inboundGroupSession exportSessionAtMessageIndex:[inboundGroupSession firstKnownIndex] error:&error]);
}

// =============================================>
// Utility
// =============================================>

RCT_EXPORT_METHOD(freeUtility)
{
    NSLog(@"Free Utility");
    if (utility != nil) {
        [self freeUtility];
    }
}

RCT_EXPORT_METHOD(sha256:input resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
    NSLog(@"sha256 Utility");
    OLMUtility *utility = [[OLMUtility alloc] init].init;
    resolve([utility sha256:input]);
}

RCT_EXPORT_METHOD(ed25519_verify:key message:(NSString *)message signature:(NSString *)signature resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
    NSLog(@"ed25519_verify Utility");
    OLMUtility *utility = [[OLMUtility alloc] init].init;
    NSError *error;
    resolve([utility verifyEd25519Signature:signature key:key message:message error:&error] ? (@"true") : (@"false"));
}

// =============================================>
// Session
// =============================================>

RCT_EXPORT_METHOD(freeSession)
{
    NSLog(@"Free Session");
    if (session != nil) {
        [self freeSession];
    }
}

RCT_EXPORT_METHOD(pickleSession:key resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject)
{
    NSLog(@"Pickle Session");
    NSError *error;
    resolve([session serializeDataWithKey:key error:&error]);
    
}

RCT_EXPORT_METHOD(unpickleSession:key pickle:pickle resolver:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject)
{
    NSLog(@"Unpickle Session");
    NSError *error;
    resolve([session initWithSerializedData:pickle key:key error:&error]);
}

RCT_EXPORT_METHOD(createOutboundSession:account their_identity_key:their_identity_key their_one_time_key:their_one_time_key)
{
    NSLog(@"Create Outbound Session");
    NSError *error;
    [self setSession:[[OLMSession alloc] initOutboundSessionWithAccount:account theirIdentityKey:their_identity_key theirOneTimeKey:their_one_time_key error:&error]];
}

RCT_EXPORT_METHOD(createInboundSession:account one_time_key_message:one_time_key_message)
{
    NSLog(@"Create Inbound Session");
    NSError *error;
    [self setSession:[[OLMSession alloc] initInboundSessionWithAccount:account oneTimeKeyMessage:one_time_key_message error:&error]];
}

RCT_EXPORT_METHOD(createInboundSessionFrom:account identity_key:identity_key one_time_key_message:one_time_key_message)
{
    NSLog(@"Create Inbound Session From");
    NSError *error;
    [self setSession:[[OLMSession alloc] initInboundSessionWithAccount:account theirIdentityKey:identity_key oneTimeKeyMessage:one_time_key_message error:&error]];
}

RCT_EXPORT_METHOD(getSessionId: resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
    NSLog(@"Get Session Id");
    resolve(session.sessionIdentifier);
}

RCT_EXPORT_METHOD(sessionHasReceivedMessage: resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
    NSLog(@"Session Has Received Message");
    // todo
    resolve(NULL);
}

RCT_EXPORT_METHOD(sessionMatchesInbound:one_time_key_message resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
    NSLog(@"Session Matches Inbound");
    resolve([session matchesInboundSession:one_time_key_message] ? (@"true") : (@"false"));
}

RCT_EXPORT_METHOD(sessionMatchesInboundFrom:identity_key one_time_key_message:one_time_key_message resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
    NSLog(@"Session Matches Inbound From");
    resolve([session matchesInboundSessionFrom:identity_key oneTimeKeyMessage:one_time_key_message] ? (@"true") : (@"false"));
}

RCT_EXPORT_METHOD(encryptSession:plaintext resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
    NSLog(@"Encrypt Session");
    NSError *error;
    resolve([session encryptMessage:plaintext error:&error]);
}

RCT_EXPORT_METHOD(decryptSession:message_type message:message resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
    NSLog(@"Decrypt Session");
    NSError *error;
    resolve([session decryptMessage:message error:&error]);
}

RCT_EXPORT_METHOD(describeSession: resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
    NSLog(@"Describe Session");
    resolve(@"NOT IMPLEMENTED");
}

// =============================================>
// SAS
// =============================================>

RCT_EXPORT_METHOD(createSAS)
{
    NSLog(@"Create SAS");
    olmSAS = [[OLMSAS alloc] init];
}

RCT_EXPORT_METHOD(freeSAS)
{
    NSLog(@"Free SAS");
    if (olmSAS != nil) {
        [self freeSAS];
    }
}

RCT_EXPORT_METHOD(getSASPubKey: resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
    NSLog(@"Get SAS Public Key");
    resolve([olmSAS publicKey]);
}

RCT_EXPORT_METHOD(setTheirSASKey:their_key)
{
    NSLog(@"Set Their SAS Key");
    [olmSAS setTheirPublicKey:their_key];
}

RCT_EXPORT_METHOD(generateSASBytes:info length:length resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
    NSLog(@"Generate SAS Bytes");
    resolve([olmSAS generateBytes:info length:(NSUInteger)length]);
}

RCT_EXPORT_METHOD(calculateSASMac:input info:info resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
    NSLog(@"Calculate SAS Mac");
    resolve([olmSAS calculateMac:input info:info error:NULL]);
}

RCT_EXPORT_METHOD(calculateSASMacLongKdf:input info:info resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
    NSLog(@"Calculate SAS Mac");
    resolve([olmSAS calculateMacLongKdf:input info:info error:NULL]);
}

// =============================================>
// PKSigning
// =============================================>

RCT_EXPORT_METHOD(createPkSigning)
{
    NSLog(@"Create PK Signing");
    olmPkSigning = [[OLMPkSigning alloc] init];
}

RCT_EXPORT_METHOD(freePkSigning)
{
    NSLog(@"Free PK Signing");
    if (olmPkSigning != nil) {
        [self freePkSigning];
    }
}

RCT_EXPORT_METHOD(initPkSigningWithSeed:seed resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
    NSLog(@"Init PK Signing With Seed");
    resolve([olmPkSigning doInitWithSeed:seed error:NULL]);
}

RCT_EXPORT_METHOD(generatePkSigningSeed:seed resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
    NSLog(@"Generate PK Signing With Seed");
    // TODO: this is the Java version
//    ConversionUtils.convertByteArrayToArray(OlmPkSigning.generateSeed())
    
//    resolve([olmPkSigning seed:seed error:NULL]);
}

RCT_EXPORT_METHOD(signPkSigning:message resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
    NSLog(@"Sign PK Signing");
    resolve([olmPkSigning sign:message error:NULL]);
}

// =============================================>
// PKEncryption
// =============================================>

RCT_EXPORT_METHOD(createPkEncryption)
{
    NSLog(@"Create PK Encryption");
    olmPkEncryption = [[OLMPkEncryption alloc] init];
}

RCT_EXPORT_METHOD(freePkEncryption)
{
    NSLog(@"Free PK Encryption");
    if (olmPkEncryption != nil) {
        [self freePkEncryption];
    }
}

RCT_EXPORT_METHOD(setPkEncryptionRecipientKey:key)
{
    NSLog(@"Set PK Encryption Recipient Key");
    [olmPkEncryption setRecipientKey:key];
}

RCT_EXPORT_METHOD(encryptPkEncryption:plaintext resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
    NSLog(@"Encrypt PK Encryption");
    // TODO: this is different from the java so more is maybe needed
    resolve([olmPkEncryption encryptMessage:plaintext error:NULL]);
}

// =============================================>
// PKDecryption
// =============================================>

RCT_EXPORT_METHOD(createPkDecryption)
{
    NSLog(@"Create PK Decryption");
    olmPkDecryption = [[OLMPkDecryption alloc] init];
}

RCT_EXPORT_METHOD(freePkDecryption)
{
    NSLog(@"Free PK Decryption");
    if (olmPkDecryption != nil) {
        [self freePkDecryption];
    }
}

RCT_EXPORT_METHOD(initPkDecryptionWithPrivateKey:key resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
    NSLog(@"init PK Decryption with private key");
    NSError *error;
    resolve([olmPkDecryption setPrivateKey:key error:&error]);
}

RCT_EXPORT_METHOD(generatePkDecryptionKey:key resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
    NSLog(@"generate PK decryption key");
    NSError *error;
    resolve([olmPkDecryption generateKey:&error]);
}

RCT_EXPORT_METHOD(getPkDecryptionPrivateKey: resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
    NSLog(@"get PK Decryption private key");
    resolve([olmPkDecryption privateKey]);
}

RCT_EXPORT_METHOD(picklePkDecryption:key resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
    NSLog(@"pickle pk decryption key");
    NSError *error;
    resolve([olmPkDecryption serializeDataWithKey:key error:&error]);
}

RCT_EXPORT_METHOD(unpicklePkDecryption:key pickle:pickle resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
    NSLog(@"unpickle pk decryption key");
    NSError *error;
    resolve([olmPkDecryption initWithSerializedData:pickle key:key error:&error]);
}

RCT_EXPORT_METHOD(decryptPkDecryption:ephemeral_key mac:mac ciphertext:ciphertext resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
    NSLog(@"decrypt PK Decryption");
    OLMPkMessage *msg = [[OLMPkMessage alloc] init];
    msg = [msg initWithCiphertext:ciphertext mac:mac ephemeralKey:ephemeral_key];
    NSError *error;
    resolve([olmPkDecryption decryptMessage:msg error:&error]);
}

RCT_EXPORT_METHOD(PRIVATE_KEY_LENGTH: resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
    NSLog(@"private key length");
    resolve([NSString stringWithFormat:@"%@", [olmPkDecryption privateKey].length]);
}

// =============================================>
// Outbound Group Session
// =============================================>

RCT_EXPORT_METHOD(freeOutboundGroupSession)
{
    NSLog(@"Free Outbound Group Session");
    if (olmOutboundGroupSession != nil) {
        [self freeOutboundGroupSession];
    }
}

RCT_EXPORT_METHOD(pickleOutboundGroupSession:key resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
    NSLog(@"pickle outbound group session");
    NSError *error;
    resolve([olmOutboundGroupSession serializeDataWithKey:key error:&error]);
}

RCT_EXPORT_METHOD(unpickleOutboundGroupSession:key pickle:pickle resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
    NSLog(@"unpickle outbound group session");
    NSError *error;
    resolve([olmOutboundGroupSession initWithSerializedData:pickle key:key error:&error]);
}

RCT_EXPORT_METHOD(createOutboundGroupSession)
{
    NSLog(@"create OutboundGroupSession");
    olmOutboundGroupSession = [[OLMOutboundGroupSession alloc] init];
    
}

RCT_EXPORT_METHOD(encryptOutboundGroupSession:plaintext resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
    NSLog(@"encrypt Outbound Group Session");
    NSError *error;
    resolve([olmOutboundGroupSession encryptMessage:plaintext error:&error]);
}

RCT_EXPORT_METHOD(getOutboundGroupSessionSessionId: resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
    NSLog(@"get Outbound Group Session session ID");
    resolve([olmOutboundGroupSession sessionIdentifier]);
}

RCT_EXPORT_METHOD(getOutboundGroupSessionSessionKey: resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
    NSLog(@"get Outbound Group Session session key");
    resolve([olmOutboundGroupSession sessionKey]);
}

RCT_EXPORT_METHOD(getOutboundGroupSessionMessageIndex: resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
    NSLog(@"get Outbound Group Session message index");
    resolve([NSString stringWithFormat:@"%@", [olmOutboundGroupSession messageIndex]]);
}

@end
