package eu.teraflow.automation;

import context.ContextOuterClass;
import context.ContextOuterClass.DeviceId;
import context.ContextOuterClass.DeviceOperationalStatusEnum;
import context.ContextOuterClass.Uuid;
import eu.teraflow.automation.context.model.Event;
import eu.teraflow.automation.context.model.EventTypeEnum;
import eu.teraflow.automation.device.model.ConfigActionEnum;
import eu.teraflow.automation.device.model.ConfigRule;
import eu.teraflow.automation.device.model.Device;
import eu.teraflow.automation.device.model.DeviceConfig;
import eu.teraflow.automation.device.model.DeviceEvent;
import eu.teraflow.automation.device.model.DeviceOperationalStatus;
import java.util.stream.Collectors;
import javax.inject.Singleton;

@Singleton
public class Serializer {

    public String deserialize(DeviceId deviceId) {
        return deviceId.getDeviceUuid().getUuid();
    }

    public DeviceId serializeDeviceId(String expectedDeviceId) {
        final var uuid = Uuid.newBuilder().setUuid(expectedDeviceId).build();

        return ContextOuterClass.DeviceId.newBuilder().setDeviceUuid(uuid).build();
    }

    public ContextOuterClass.EventTypeEnum serialize(EventTypeEnum eventTypeEnum) {
        switch (eventTypeEnum) {
            case CREATE:
                return ContextOuterClass.EventTypeEnum.EVENTTYPE_CREATE;
            case REMOVE:
                return ContextOuterClass.EventTypeEnum.EVENTTYPE_REMOVE;
            case UPDATE:
                return ContextOuterClass.EventTypeEnum.EVENTTYPE_UPDATE;
            case UNDEFINED:
                return ContextOuterClass.EventTypeEnum.EVENTTYPE_UNDEFINED;
            default:
                return ContextOuterClass.EventTypeEnum.UNRECOGNIZED;
        }
    }

    public EventTypeEnum deserialize(ContextOuterClass.EventTypeEnum serializedEventType) {
        switch (serializedEventType) {
            case EVENTTYPE_CREATE:
                return EventTypeEnum.CREATE;
            case EVENTTYPE_REMOVE:
                return EventTypeEnum.REMOVE;
            case EVENTTYPE_UPDATE:
                return EventTypeEnum.UPDATE;
            case EVENTTYPE_UNDEFINED:
            case UNRECOGNIZED:
            default:
                return EventTypeEnum.UNDEFINED;
        }
    }

    public ContextOuterClass.Event serialize(Event event) {
        final var builder = ContextOuterClass.Event.newBuilder();

        final var eventType = serialize(event.getEventTypeEnum());
        builder.setEventType(eventType);
        builder.setTimestamp(event.getTimestamp());

        return builder.build();
    }

    public Event deserialize(ContextOuterClass.Event serializedEvent) {
        final var timestamp = serializedEvent.getTimestamp();
        final var eventType = deserialize(serializedEvent.getEventType());

        return new Event(timestamp, eventType);
    }

    public ContextOuterClass.DeviceEvent serialize(DeviceEvent deviceEvent) {
        final var builder = ContextOuterClass.DeviceEvent.newBuilder();

        builder.setDeviceId(serializeDeviceId(deviceEvent.getDeviceId()));
        builder.setEvent(serialize(deviceEvent.getEvent()));

        return builder.build();
    }

    public DeviceEvent deserialize(ContextOuterClass.DeviceEvent deviceEvent) {
        final var deviceId = deserialize(deviceEvent.getDeviceId());
        final var event = deserialize(deviceEvent.getEvent());

        return new DeviceEvent(deviceId, event);
    }

    public ContextOuterClass.ConfigActionEnum serialize(ConfigActionEnum configAction) {
        switch (configAction) {
            case SET:
                return ContextOuterClass.ConfigActionEnum.CONFIGACTION_SET;
            case DELETE:
                return ContextOuterClass.ConfigActionEnum.CONFIGACTION_DELETE;
            case UNDEFINED:
            default:
                return ContextOuterClass.ConfigActionEnum.CONFIGACTION_UNDEFINED;
        }
    }

    public ConfigActionEnum deserialize(ContextOuterClass.ConfigActionEnum serializedConfigAction) {
        switch (serializedConfigAction) {
            case CONFIGACTION_SET:
                return ConfigActionEnum.SET;
            case CONFIGACTION_DELETE:
                return ConfigActionEnum.DELETE;
            case UNRECOGNIZED:
            case CONFIGACTION_UNDEFINED:
            default:
                return ConfigActionEnum.UNDEFINED;
        }
    }

    public ContextOuterClass.ConfigRule serialize(ConfigRule configRule) {
        final var builder = ContextOuterClass.ConfigRule.newBuilder();

        builder.setAction(serialize(configRule.getConfigActionEnum()));
        builder.setResourceKey(configRule.getResourceKey());
        builder.setResourceValue(configRule.getResourceValue());

        return builder.build();
    }

    public ConfigRule deserialize(ContextOuterClass.ConfigRule configRule) {
        final var configActionEnum = deserialize(configRule.getAction());

        return new ConfigRule(
                configActionEnum, configRule.getResourceKey(), configRule.getResourceValue());
    }

    public ContextOuterClass.DeviceConfig serialize(DeviceConfig deviceConfig) {
        final var builder = ContextOuterClass.DeviceConfig.newBuilder();

        final var serializedConfigRules =
                deviceConfig.getConfigRules().stream().map(this::serialize).collect(Collectors.toList());
        builder.addAllConfigRules(serializedConfigRules);

        return builder.build();
    }

    public DeviceConfig deserialize(ContextOuterClass.DeviceConfig deviceConfig) {
        final var configRules =
                deviceConfig.getConfigRulesList().stream()
                        .map(this::deserialize)
                        .collect(Collectors.toList());

        return new DeviceConfig(configRules);
    }

    public ContextOuterClass.DeviceOperationalStatusEnum serialize(DeviceOperationalStatus opStatus) {
        switch (opStatus) {
            case ENABLED:
                return DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_ENABLED;
            case DISABLED:
                return DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_DISABLED;
            case UNDEFINED:
            default:
                return DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_UNDEFINED;
        }
    }

    public DeviceOperationalStatus deserialize(
            ContextOuterClass.DeviceOperationalStatusEnum opStatus) {
        switch (opStatus) {
            case DEVICEOPERATIONALSTATUS_ENABLED:
                return DeviceOperationalStatus.ENABLED;
            case DEVICEOPERATIONALSTATUS_DISABLED:
                return DeviceOperationalStatus.DISABLED;
            case DEVICEOPERATIONALSTATUS_UNDEFINED:
            case UNRECOGNIZED:
            default:
                return DeviceOperationalStatus.UNDEFINED;
        }
    }

    public ContextOuterClass.Device serialize(Device device) {
        final var builder = ContextOuterClass.Device.newBuilder();

        builder.setDeviceId(serializeDeviceId(device.getDeviceId()));
        builder.setDeviceType(device.getDeviceType());
        builder.setDeviceConfig(serialize(device.getDeviceConfig()));
        builder.setDeviceOperationalStatus(serialize(device.getDeviceOperationalStatus()));

        return builder.build();
    }

    public Device deserialize(ContextOuterClass.Device device) {
        final var id = deserialize(device.getDeviceId());
        final var type = device.getDeviceType();
        final var config = deserialize(device.getDeviceConfig());
        final var operationalStatus = deserialize(device.getDeviceOperationalStatus());

        return new Device(id, type, config, operationalStatus);
    }

    public Uuid serializeUuid(String uuid) {
        return Uuid.newBuilder().setUuid(uuid).build();
    }

    public String deserialize(Uuid uuid) {
        return uuid.getUuid();
    }
}
