#include "ua_securechannel_manager.h"
#include "ua_session.h"
#include "ua_util.h"
#include "ua_statuscodes.h"

struct channel_list_entry {
    UA_SecureChannel channel;
    LIST_ENTRY(channel_list_entry) pointers;
};

struct UA_SecureChannelManager {
    UA_Int32    maxChannelCount;
    UA_DateTime maxChannelLifetime;
    LIST_HEAD(channel_list, channel_list_entry) channels;
    UA_MessageSecurityMode securityMode;
    UA_String   endpointUrl;
    UA_DateTime channelLifeTime;
    UA_Int32    lastChannelId;
    UA_UInt32   lastTokenId;
};

UA_StatusCode UA_SecureChannelManager_new(UA_SecureChannelManager **cm, UA_UInt32 maxChannelCount,
                                          UA_UInt32 tokenLifetime, UA_UInt32 startChannelId,
                                          UA_UInt32 startTokenId, UA_String *endpointUrl) {
    if(!(*cm = UA_alloc(sizeof(UA_SecureChannelManager))))
        return UA_STATUSCODE_BADOUTOFMEMORY;
    UA_SecureChannelManager *channelManager = *cm;
    LIST_INIT(&channelManager->channels);
    channelManager->lastChannelId      = startChannelId;
    channelManager->lastTokenId        = startTokenId;
    UA_String_copy(endpointUrl, &channelManager->endpointUrl);
    channelManager->maxChannelLifetime = tokenLifetime;
    channelManager->maxChannelCount    = maxChannelCount;
    return UA_STATUSCODE_GOOD;
}

void UA_SecureChannelManager_delete(UA_SecureChannelManager *cm) {
    struct channel_list_entry *entry = LIST_FIRST(&cm->channels);
    while(entry) {
        LIST_REMOVE(entry, pointers);
        if(entry->channel.session)
            entry->channel.session->channel = UA_NULL;
        if(entry->channel.connection)
            entry->channel.connection->channel = UA_NULL;
        UA_SecureChannel_deleteMembers(&entry->channel);
        UA_free(entry);
        entry = LIST_FIRST(&cm->channels);
    }
    UA_String_deleteMembers(&cm->endpointUrl);
    UA_free(cm);
}

UA_StatusCode UA_SecureChannelManager_open(UA_SecureChannelManager           *cm,
                                           UA_Connection                     *conn,
                                           const UA_OpenSecureChannelRequest *request,
                                           UA_OpenSecureChannelResponse      *response) {
    struct channel_list_entry *entry = UA_alloc(sizeof(struct channel_list_entry));
    UA_SecureChannel_init(&entry->channel);

    entry->channel.connection = conn;
    entry->channel.securityToken.channelId       = cm->lastChannelId++;
    entry->channel.securityToken.tokenId         = cm->lastTokenId++;
    entry->channel.securityToken.createdAt       = UA_DateTime_now();
    entry->channel.securityToken.revisedLifetime =
        request->requestedLifetime > cm->maxChannelLifetime ?
        cm->maxChannelLifetime : request->requestedLifetime;

    switch(request->securityMode) {
    case UA_SECURITYMODE_INVALID:
        printf("UA_SecureChannel_processOpenRequest - client demands invalid \n");
        break;

    case UA_SECURITYMODE_NONE:
        UA_ByteString_copy(&request->clientNonce, &entry->channel.clientNonce);
        break;

    case UA_SECURITYMODE_SIGNANDENCRYPT:
        printf("UA_SecureChannel_processOpenRequest - client demands signed & encrypted \n");
        //TODO check if senderCertificate and ReceiverCertificateThumbprint are present
        break;
    }

    UA_String_copycstring("http://opcfoundation.org/UA/SecurityPolicy#None",
                          (UA_String *)&entry->channel.serverAsymAlgSettings.securityPolicyUri);
    LIST_INSERT_HEAD(&cm->channels, entry, pointers);

    response->serverProtocolVersion = 0;
    UA_SecureChannel_generateNonce(&entry->channel.serverNonce);
    UA_ByteString_copy(&entry->channel.serverNonce, &response->serverNonce);
    UA_ChannelSecurityToken_copy(&entry->channel.securityToken, &response->securityToken);

    conn->channel = &entry->channel;

    return UA_STATUSCODE_GOOD;
}

UA_StatusCode UA_SecureChannelManager_renew(UA_SecureChannelManager           *cm,
                                            UA_Connection                     *conn,
                                            const UA_OpenSecureChannelRequest *request,
                                            UA_OpenSecureChannelResponse      *response) {

    UA_SecureChannel *channel = conn->channel;
    if(channel == UA_NULL)
        return UA_STATUSCODE_BADINTERNALERROR;

    channel->securityToken.tokenId         = cm->lastTokenId++;
    channel->securityToken.createdAt       = UA_DateTime_now(); // todo: is wanted?
    channel->securityToken.revisedLifetime = request->requestedLifetime > cm->maxChannelLifetime ?
                                             cm->maxChannelLifetime : request->requestedLifetime;

    UA_SecureChannel_generateNonce(&channel->serverNonce);
    UA_ByteString_copy(&channel->serverNonce, &response->serverNonce);
    UA_ChannelSecurityToken_copy(&channel->securityToken, &response->securityToken);

    return UA_STATUSCODE_GOOD;
}

UA_StatusCode UA_SecureChannelManager_get(UA_SecureChannelManager *cm, UA_UInt32 channelId,
                                          UA_SecureChannel **channel) {
    struct channel_list_entry *entry;
    LIST_FOREACH(entry, &cm->channels, pointers) {
        if(entry->channel.securityToken.channelId == channelId) {
            *channel = &entry->channel;
            return UA_STATUSCODE_GOOD;
        }
    }
    *channel = UA_NULL;
    return UA_STATUSCODE_BADINTERNALERROR;
}

UA_StatusCode UA_SecureChannelManager_close(UA_SecureChannelManager *cm, UA_UInt32 channelId) {
    //TODO lock access
    // TODO: close the binaryconnection if it is still open. So we dö not have stray pointers..
    struct channel_list_entry *entry;
    LIST_FOREACH(entry, &cm->channels, pointers) {
        if(entry->channel.securityToken.channelId == channelId) {
            UA_SecureChannel_deleteMembers(&entry->channel);
            LIST_REMOVE(entry, pointers);
            UA_free(entry);
            return UA_STATUSCODE_GOOD;
        }
    }
    //TODO notify server application that secureChannel has been closed part 6 - §7.1.4
    return UA_STATUSCODE_BADINTERNALERROR;
}