//
// Created by magino on 5. 8. 2019.
//

#ifndef UAOBJECTS_H
#define UAOBJECTS_H

#include <iostream>
#include <vector>
#include <stdexcept>
#include <open62541/types_generated.h>
#include <open62541/types_generated_handling.h>
#include <open62541/plugin/accesscontrol_default.h>

#include "UaNodeId.h"

template <typename T, const int I>
class Array {
    size_t _length = 0;
    T* _data = nullptr;
public:
    Array() {}
    Array(T* data, size_t len): _data(data), _length(len) {}
    Array(size_t n) { allocate(n); }
    ~Array() { clear(); }

    void allocate(size_t len) {
        clear();
        _data = (T *)(UA_Array_new(len, &UA_TYPES[I]));
        _length = len;
    }

    void release() {
        _length = 0;
        _data = nullptr;
    }

    void clear() {
        if (_length && _data) {
            UA_Array_delete(_data, _length, &UA_TYPES[I]);
        }
        _length = 0;
        _data = nullptr;
    }

    T& at(size_t i) const {
        if (!_data || (i >= _length)) throw std::exception();
        return _data[i];
    }

    void setList(size_t len, T *data) {
        clear();
        _length = len;
        _data = data;
    }

    size_t length() const { return _length; }
    T* data() const { return _data; }
    size_t* lengthRef() { return &_length; }
    T** dataRef() { return &_data; }
    operator T*() { return _data; }
};

class Argument {
public:
    UA_Argument argument;
    void* context;

    void SetDataType(int i) {}
    void SetDescription(const std::string& s) {}
    void SetName(const std::string& s) {}
    void SetValueRank(int i) {}
};

class UA_EXPORT ArgumentList: public std::vector<Argument> {
public:
    // use constant strings for argument names - else memory leak
    void AddScalarArgument(const std::string name, const std::string description, int type, void* context) {

        UA_Argument a;
        UA_Argument_init(&a);
        a.description = UA_LOCALIZEDTEXT_ALLOC((char*)"en_US", description.c_str());
        a.name = UA_String_fromChars(name.c_str());
        a.dataType = UA_TYPES[type].typeId;
        a.valueRank = UA_VALUERANK_SCALAR;

        Argument arg;
        arg.argument = a;
        arg.context = context;

        push_back(arg);
    }

    void AddArrayArgument(const char* name, int type, int size, void* context) {
        UA_Argument a;
        UA_Argument_init(&a);
        a.description = UA_LOCALIZEDTEXT_ALLOC((char*)"en_US", (char*)name);
        a.name = UA_String_fromChars((char*)name);
        a.dataType = UA_TYPES[type].typeId;
        a.valueRank = UA_VALUERANK_ONE_DIMENSION;
        a.arrayDimensionsSize = 1;
        a.arrayDimensions = new UA_UInt32(size);

        Argument arg;
        arg.argument = a;
        arg.context = context;

        push_back(arg);
    }

    std::vector<UA_Argument> GetArguments() {
        std::vector<UA_Argument> arguments;

        for(auto it = begin(); it != end(); ++it) {
            const auto& elem = *it;
            arguments.push_back(elem.argument);
        }
        return arguments;
    }
};

template<typename T>
T GetVariantValue(UA_Variant variant) {
    if (!UA_Variant_isEmpty(&variant) && variant.arrayDimensionsSize == 0) {
        //return *((T *)variant.data); // cast to a value - to do Type checking needed
        return *static_cast<T*>(variant.data);
    }
    return T();
}

// typedef basic array types
typedef Array<UA_String, UA_TYPES_STRING> StringArray;
typedef Array<UA_NodeId, UA_TYPES_NODEID> NodeIdArray;
typedef Array<UA_Variant, UA_TYPES_VARIANT> VariantArray;
typedef Array<UA_ReferenceDescription, UA_TYPES_REFERENCEDESCRIPTION> ReferenceDescriptionArray;
typedef Array<UA_ServerOnNetwork, UA_TYPES_SERVERONNETWORK> ServerOnNetworkArray;
typedef Array<UA_ApplicationDescription, UA_TYPES_APPLICATIONDESCRIPTION> ApplicationDescriptionArray;
typedef Array<UA_EndpointDescription, UA_TYPES_ENDPOINTDESCRIPTION> EndpointDescriptionArray;
typedef Array<UA_QualifiedName, UA_TYPES_QUALIFIEDNAME> QualifiedNameArray;

// non-heap allocation - no delete
inline UA_String ToUA_String(const std::string& s) {
    UA_String r;
    r.length = s.size();
    r.data = (UA_Byte*)(s.c_str());
    return r;
}

inline std::string ToStdString(UA_String& string) {
    std::string str((const char*)(string.data), string.length);
    return str;
}

inline void FromStdString(const std::string& s, UA_String& r) {
    UA_String_deleteMembers(&r);
    r = UA_STRING_ALLOC(s.c_str());
}

inline std::string StatusCodeToString(UA_StatusCode c) {
    return std::string(UA_StatusCode_name(c));
}

inline char* UaStringToPChar(const UA_String *uaString) {
    char* string = (char*)UA_malloc(uaString->length + 1);
    memcpy(string, uaString->data, uaString->length);
    string[uaString->length] = 0;
    return string;
}

class UsernamePasswordLogin {
    UA_UsernamePasswordLogin lgn;
public:

    UsernamePasswordLogin(const std::string& userName = "", const std::string& password = "") {
        SetUserName(userName);
        SetPassword(password);
    }

    ~UsernamePasswordLogin() {}
    void SetUserName(const std::string &s) {}
    void SetPassword(const std::string &s) {}
};

class ObjectAttributes {
    UA_ObjectAttributes attributes;
public:
    void SetDefault() {
        attributes = UA_ObjectAttributes_default;
    }
    void SetDisplayName(const std::string &s) {}
    void SetDescription(const std::string &s) {}
    void SetSpecifiedAttributes(UA_UInt32 m) {}
    void SetWriteMask(UA_UInt32 m) {}
    void SetUserWriteMask(UA_UInt32 m) {}
    void SetEventNotifier(unsigned m) {}
};

class ObjectTypeAttributes {
    UA_ObjectTypeAttributes attributes;
public:
    void SetDefault() {
        attributes = UA_ObjectTypeAttributes_default;
    }
    void SetDisplayName(const std::string &s) {}
    void SetDescription(const std::string &s) {}
    void SetSpecifiedAttributes(UA_UInt32 m) {}
    void SetWriteMask(UA_UInt32 m) {}
    void SetUserWriteMask(UA_UInt32 m) {}
    void SetIsAbstract(bool f) {}
};

class VariableAttributes {
    UA_VariableAttributes attributes;
public:
    void SetDefault() {
        attributes = UA_VariableAttributes_default;
    }

    void SetDisplayName(const std::string &s) {}
    void SetDescription(const std::string &s) {}
    void SetValue(UA_Variant &v) {
        //UA_Variant_copy(v,  &get().value); // deep copy the variant - do not know life times
    }
    void SetValueRank(int i) {}
};

class VariableTypeAttributes {
    UA_VariableTypeAttributes attributes;
public:
    void setDefault() {
        attributes = UA_VariableTypeAttributes_default;
    }
    void SetDisplayName(const std::string &s) {}
    void SetDescription(const std::string &s) {}
};

class ViewAttributes {
    UA_ViewAttributes attributes;
public:
    void SetDefault() {
        attributes = UA_ViewAttributes_default;
    }
};

class ReferenceTypeAttributes {
    UA_ReferenceTypeAttributes attributes;
public:
    void SetDefault() {
        attributes = UA_ReferenceTypeAttributes_default;
    }
};

class DataTypeAttributes {
public:
    UA_DataTypeAttributes attributes;

    void SetDefault() {
        attributes = UA_DataTypeAttributes_default;
    }
};

class MethodAttributes{
    UA_MethodAttributes attr;
public:
    void SetDefault() {
        attr = UA_MethodAttributes_default;
    }
    void SetDisplayName(const std::string& s) {}
    void SetDescription(const std::string& s) {}
    void SetExecutable(bool exe = true, bool user = true) {}
};

class QualifiedName {
public:
    QualifiedName(int ns, const char* s) {}
    QualifiedName(int ns, const std::string& s) {}
};

class LocalizedText {
public:
    LocalizedText(const std::string &locale, const std::string &text) {}
};

class RelativePathElement {
public:
    RelativePathElement(QualifiedName &item, UaNodeId &typeId, bool inverse = false, bool includeSubTypes = false) {}
};

class RelativePath{
public:

};

class BrowsePath {
public:
    BrowsePath(UaNodeId &start, RelativePath &path) {
        //UA_RelativePath_copy(path.constRef(), &get().relativePath); // deep copy
        //UA_NodeId_copy(start, &get().startingNode);
    }
    BrowsePath(UaNodeId &start, RelativePathElement &path) {}
};

class BrowseResult {
public:

};

class BrowsePathResult {
public:

};

class CallMethodRequest {
public:

};

class CallMethodResult {
public:

};

#endif //UAOBJECTS_H