package com.inductiveautomation.opcua.stack.core.types.builtin;

import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.Objects;

import com.google.common.base.Objects.ToStringHelper;
import com.inductiveautomation.opcua.stack.core.serialization.UaEnumeration;
import com.inductiveautomation.opcua.stack.core.serialization.UaStructure;
import com.inductiveautomation.opcua.stack.core.util.ArrayUtil;

import static com.google.common.base.Preconditions.checkArgument;

public class Variant {

    public static final Variant NullValue = new Variant(null);

    private final Object value;

    /**
     * Create a new Variant with a given value.
     *
     * @param value the value this Variant holds.
     */
    public Variant(@Nullable Object value) {
        if (value != null) {
            Class<?> clazz = value.getClass().isArray() ? ArrayUtil.getType(value) : value.getClass();

            checkArgument(clazz.isArray() || !Variant.class.equals(clazz), "Variant cannot contain Variant");
            checkArgument(!DataValue.class.equals(clazz), "Variant cannot contain DataValue");
            checkArgument(!DiagnosticInfo.class.equals(clazz), "Variant cannot contain DiagnosticInfo");
        }

//        if (value instanceof UaEnumeration) {
//            value = ((UaEnumeration) value).getValue();
//        }
//
//        else if (value instanceof UaStructure) {
//            value = new ExtensionObject((UaStructure) value);
//        }
//
//        else if (value instanceof UaStructure[]) {
//            UaStructure[] values = (UaStructure[]) value;
//            ExtensionObject[] xos = new ExtensionObject[values.length];
//
//            for (int i = 0; i < values.length; i++) {
//                xos[i] = new ExtensionObject(values[i]);
//            }
//
//            value = xos;
//        }

        this.value = value;
    }

    public Object getValue() {
        return value;
    }

    public boolean isNull() {
        return value == null;
    }

    public boolean isNotNull() {
        return !isNull();
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Variant variant = (Variant) o;

        return Objects.deepEquals(value, variant.value);
    }

    @Override
    public int hashCode() {
        return Objects.hash(valueHash());
    }

    private int valueHash() {
        if (value instanceof Object[]) {
            return Arrays.deepHashCode((Object[]) value);
        } else if (value instanceof boolean[]) {
            return Arrays.hashCode((boolean[]) value);
        } else if (value instanceof byte[]) {
            return Arrays.hashCode((byte[]) value);
        } else if (value instanceof short[]) {
            return Arrays.hashCode((short[]) value);
        } else if (value instanceof int[]) {
            return Arrays.hashCode((int[]) value);
        } else if (value instanceof long[]) {
            return Arrays.hashCode((long[]) value);
        } else if (value instanceof float[]) {
            return Arrays.hashCode((float[]) value);
        } else if (value instanceof double[]) {
            return Arrays.hashCode((double[]) value);
        } else {
            return Objects.hashCode(value);
        }
    }

    @Override
    public String toString() {
        ToStringHelper helper = com.google.common.base.Objects.toStringHelper(this);

        helper.add("value", value);

        return helper.toString();
    }

}

@@ -142,16 +142,43 @@
 Objects;
+%0Aimport java.util.Optional;
 %0A%0Aimport
@@ -225,16 +225,77 @@
 Helper;%0A
+import com.inductiveautomation.opcua.stack.core.Identifiers;%0A
 import c
@@ -501,16 +501,79 @@
 rayUtil;
+%0Aimport com.inductiveautomation.opcua.stack.core.util.TypeUtil;
 %0A%0Aimport
@@ -1374,26 +1374,24 @@
      %7D%0A%0A
-//
         
 if (valu
@@ -1386,56 +1386,87 @@
     
-if (value instanceof UaEnumeration
+this.value = value;%0A    %7D%0A%0A    public Optional%3CNodeId%3E getDataType(
 ) %7B%0A
-//
         
     
@@ -1465,81 +1465,61 @@
     
-    
+if (
 value =
- ((UaEnumeration) value).getValue();%0A//        %7D%0A//%0A//
+= null) return Optional.empty();%0A%0A
         
 else
@@ -1506,37 +1506,32 @@
 pty();%0A%0A        
-else 
 if (value instan
@@ -1546,26 +1546,24 @@
 tructure) %7B%0A
-//
             
@@ -1566,82 +1566,72 @@
     
-value = new ExtensionObject((UaStructure) value);%0A//        %7D%0A//%0A//       
+return Optional.of(((UaStructure) value).getTypeId());%0A        %7D
  els
@@ -1659,25 +1659,23 @@
 f Ua
-Structure%5B%5D
+Enumeration
 ) %7B%0A
-//
     
@@ -1686,248 +1686,189 @@
     
-UaStructure%5B%5D values = (UaStructure%5B%5D) value;%0A//            ExtensionObject%5B%5D xos = new ExtensionObject%5Bvalues.length%5D;%0A//%0A//            for (int i = 0; i %3C values.length; i++) %7B%0A//                xos%5Bi%5D = new ExtensionObject(values%5Bi%5D);%0A//
+return Optional.of(Identifiers.Int32);%0A        %7D else %7B%0A            Class%3C?%3E clazz = value.getClass().isArray() ?%0A                    ArrayUtil.getType(value) : value.getClass();%0A%0A 
     
@@ -1878,51 +1878,102 @@
     
- %7D%0A//%0A//            value = xos;%0A//
+int typeId = TypeUtil.getBuiltinTypeId(clazz);%0A%0A            return typeId == -1 ?%0A    
         
 %7D%0A%0A 
@@ -1964,35 +1964,32 @@
             
-%7D%0A%0A
         
 this.value =
@@ -1976,35 +1976,80 @@
         
-this.value = value;
+Optional.empty() : Optional.of(new NodeId(0, typeId));%0A        %7D
 %0A    %7D%0A%0A

Implement Variant.getDataType()