package io.github.andrewauclair.moderndocking;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Base64;

/**
 * Base class for all Property classes
 */
public abstract class Property {
    private final String name;

    /**
     * Create a new instance with name
     *
     * @param name The name of the property
     */
    public Property(String name) {
        this.name = name;
    }

    /**
     * Get the actual type of the property
     *
     * @return The type of the property
     */
    public abstract Class<?> getType();

    /**
     * Check if the property is a null value
     *
     * @return Is the property null?
     */
    public abstract boolean isNull();

    /**
     * Get the name of the property
     *
     * @return Name of the property
     */
    public String getName() {
        return name;
    }

    /**
     * Property class that provides access to a byte
     */
    public static class ByteProperty extends Property {
        private final byte value;

        /**
         * Create a new instance
         *
         * @param name The name of the property
         * @param value The value of the property
         */
        public ByteProperty(String name, byte value) {
            super(name);
            this.value = value;
        }

        @Override
        public Class<?> getType() {
            return byte.class;
        }

        @Override
        public boolean isNull() {
            return false;
        }

        @Override
        public String toString() {
            return Byte.toString(value);
        }

        /**
         * Get the value of this property
         *
         * @return Current value
         */
        public byte getValue() {
            return value;
        }
    }

    /**
     * Property class that provides access to a short
     */
    public static class ShortProperty extends Property {
        private final short value;

        /**
         * Create a new instance
         *
         * @param name The name of the property
         * @param value The value of the property
         */
        public ShortProperty(String name, short value) {
            super(name);
            this.value = value;
        }

        @Override
        public Class<?> getType() {
            return short.class;
        }

        @Override
        public boolean isNull() {
            return false;
        }

        @Override
        public String toString() {
            return Short.toString(value);
        }

        /**
         * Get the value of this property
         *
         * @return Current value
         */
        public short getValue() {
            return value;
        }
    }

    /**
     * Property class that provides access to a int
     */
    public static class IntProperty extends Property {
        private final int value;

        /**
         * Create a new instance
         *
         * @param name The name of the property
         * @param value The value of the property
         */
        public IntProperty(String name, int value) {
            super(name);
            this.value = value;
        }

        @Override
        public Class<?> getType() {
            return int.class;
        }

        @Override
        public boolean isNull() {
            return false;
        }

        @Override
        public String toString() {
            return Integer.toString(value);
        }

        /**
         * Get the value of this property
         *
         * @return Current value
         */
        public int getValue() {
            return value;
        }
    }

    /**
     * Property class that provides access to a long
     */
    public static class LongProperty extends Property {
        private final long value;

        /**
         * Create a new instance
         *
         * @param name The name of the property
         * @param value The value of the property
         */
        public LongProperty(String name, long value) {
            super(name);
            this.value = value;
        }

        @Override
        public Class<?> getType() {
            return long.class;
        }

        @Override
        public boolean isNull() {
            return false;
        }

        @Override
        public String toString() {
            return Long.toString(value);
        }

        /**
         * Get the value of this property
         *
         * @return Current value
         */
        public long getValue() {
            return value;
        }
    }

    /**
     * Property class that provides access to a float
     */
    public static class FloatProperty extends Property {
        private final float value;

        /**
         * Create a new instance
         *
         * @param name The name of the property
         * @param value The value of the property
         */
        public FloatProperty(String name, float value) {
            super(name);
            this.value = value;
        }

        @Override
        public Class<?> getType() {
            return float.class;
        }

        @Override
        public boolean isNull() {
            return false;
        }

        @Override
        public String toString() {
            return Float.toString(value);
        }

        /**
         * Get the value of this property
         *
         * @return Current value
         */
        public float getValue() {
            return value;
        }
    }

    /**
     * Property class that provides access to a double
     */
    public static class DoubleProperty extends Property {
        private final double value;

        /**
         * Create a new instance
         *
         * @param name The name of the property
         * @param value The value of the property
         */
        public DoubleProperty(String name, double value) {
            super(name);
            this.value = value;
        }

        @Override
        public Class<?> getType() {
            return double.class;
        }

        @Override
        public boolean isNull() {
            return false;
        }

        @Override
        public String toString() {
            return Double.toString(value);
        }

        /**
         * Get the value of this property
         *
         * @return Current value
         */
        public double getValue() {
            return value;
        }
    }

    /**
     * Property class that provides access to a char
     */
    public static class CharacterProperty extends Property {
        private final char value;

        /**
         * Create a new instance
         *
         * @param name The name of the property
         * @param value The value of the property
         */
        public CharacterProperty(String name, char value) {
            super(name);
            this.value = value;
        }

        @Override
        public Class<?> getType() {
            return char.class;
        }

        @Override
        public boolean isNull() {
            return false;
        }

        @Override
        public String toString() {
            return Character.toString(value);
        }

        /**
         * Get the value of this property
         *
         * @return Current value
         */
        public char getValue() {
            return value;
        }
    }

    /**
     * Property class that provides access to a boolean
     */
    public static class BooleanProperty extends Property {
        private final boolean value;

        /**
         * Create a new instance
         *
         * @param name The name of the property
         * @param value The value of the property
         */
        public BooleanProperty(String name, boolean value) {
            super(name);
            this.value = value;
        }

        @Override
        public Class<?> getType() {
            return boolean.class;
        }

        @Override
        public boolean isNull() {
            return false;
        }

        @Override
        public String toString() {
            return Boolean.toString(value);
        }

        /**
         * Get the value of this property
         *
         * @return Current value
         */
        public boolean getValue() {
            return value;
        }
    }

    /**
     * Property class that provides access to a String
     */
    public static class StringProperty extends Property {
        private final String value;

        /**
         * Create a new instance
         *
         * @param name The name of the property
         * @param value The value of the property
         */
        public StringProperty(String name, String value) {
            super(name);
            this.value = value;
        }

        @Override
        public Class<?> getType() {
            return String.class;
        }

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

        @Override
        public String toString() {
            return value;
        }

        /**
         * Get the value of this property
         *
         * @return Current value
         */
        public String getValue() {
            return value;
        }
    }

    /**
     * Property class that provides access to a String
     */
    public static class SerializableProperty extends Property {
        private final Serializable value;

        /**
         * Create a new instance
         *
         * @param name The name of the property
         * @param value The value of the property
         */
        public SerializableProperty(String name, Serializable value) {
            super(name);
            this.value = value;
        }

        @Override
        public Class<?> getType() {
            return Serializable.class;
        }

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

        @Override
        public String toString() {
            try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
                ObjectOutputStream os = new ObjectOutputStream(bos);
                os.writeObject(value);
                return Base64.getEncoder().encodeToString(bos.toByteArray());
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

        /**
         * Get the value of this property
         *
         * @return Current value
         */
        public Serializable getValue() {
            return value;
        }
    }
}