/*
* Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package eu.teraflow.automation;

import acl.Acl;
import automation.Automation;
import context.ContextOuterClass;
import context.ContextOuterClass.ConfigRule_ACL;
import context.ContextOuterClass.ConfigRule_Custom;
import context.ContextOuterClass.ContextId;
import context.ContextOuterClass.DeviceId;
import context.ContextOuterClass.DeviceOperationalStatusEnum;
import context.ContextOuterClass.Uuid;
import eu.teraflow.automation.acl.AclAction;
import eu.teraflow.automation.acl.AclEntry;
import eu.teraflow.automation.acl.AclForwardActionEnum;
import eu.teraflow.automation.acl.AclLogActionEnum;
import eu.teraflow.automation.acl.AclMatch;
import eu.teraflow.automation.acl.AclRuleSet;
import eu.teraflow.automation.acl.AclRuleTypeEnum;
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.ConfigRuleAcl;
import eu.teraflow.automation.device.model.ConfigRuleCustom;
import eu.teraflow.automation.device.model.ConfigRuleTypeAcl;
import eu.teraflow.automation.device.model.ConfigRuleTypeCustom;
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 eu.teraflow.automation.device.model.EndPointId;
import eu.teraflow.automation.device.model.TopologyId;
import eu.teraflow.automation.model.DeviceRole;
import eu.teraflow.automation.model.DeviceRoleId;
import eu.teraflow.automation.model.DeviceRoleType;
import java.util.stream.Collectors;
import javax.inject.Singleton;

@Singleton
public class Serializer {

    public DeviceId serializeDeviceId(String expectedDeviceId) {
        final var builder = DeviceId.newBuilder();
        final var uuid = serializeUuid(expectedDeviceId);

        builder.setDeviceUuid(uuid);

        return builder.build();
    }

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

    public Automation.DeviceRoleId serialize(DeviceRoleId deviceRoleId) {
        final var builder = Automation.DeviceRoleId.newBuilder();

        final var deviceRoleDevRoleId = deviceRoleId.getId();
        final var deviceRoleDeviceId = deviceRoleId.getDeviceId();

        final var deviceRoleDevRoleIdUuid = serializeUuid(deviceRoleDevRoleId);
        final var deviceRoleDeviceIdUuid = serializeUuid(deviceRoleDeviceId);

        final var deviceId = DeviceId.newBuilder().setDeviceUuid(deviceRoleDeviceIdUuid);

        builder.setDevRoleId(deviceRoleDevRoleIdUuid);
        builder.setDevId(deviceId);

        return builder.build();
    }

    public DeviceRoleId deserialize(Automation.DeviceRoleId deviceRoleId) {
        final var devRoleId = deserialize(deviceRoleId.getDevRoleId());
        final var devId = deserialize(deviceRoleId.getDevId());

        return new DeviceRoleId(devRoleId, devId);
    }

    public Automation.DeviceRoleType serialize(DeviceRoleType deviceRoleType) {
        switch (deviceRoleType) {
            case NONE:
                return Automation.DeviceRoleType.NONE;
            case DEV_OPS:
                return Automation.DeviceRoleType.DEV_OPS;
            case DEV_CONF:
                return Automation.DeviceRoleType.DEV_CONF;
            case PIPELINE_CONF:
                return Automation.DeviceRoleType.PIPELINE_CONF;
            default:
                return Automation.DeviceRoleType.UNRECOGNIZED;
        }
    }

    public DeviceRoleType deserialize(Automation.DeviceRoleType serializedDeviceRoleType) {
        switch (serializedDeviceRoleType) {
            case DEV_OPS:
                return DeviceRoleType.DEV_OPS;
            case DEV_CONF:
                return DeviceRoleType.DEV_CONF;
            case PIPELINE_CONF:
                return DeviceRoleType.PIPELINE_CONF;
            case NONE:
            case UNRECOGNIZED:
            default:
                return DeviceRoleType.NONE;
        }
    }

    public Automation.DeviceRole serialize(DeviceRole deviceRole) {
        final var builder = Automation.DeviceRole.newBuilder();
        final var serializedDeviceRoleId = serialize(deviceRole.getDeviceRoleId());
        final var serializedDeviceRoleType = serialize(deviceRole.getType());

        builder.setDevRoleId(serializedDeviceRoleId);
        builder.setDevRoleType(serializedDeviceRoleType);

        return builder.build();
    }

    public DeviceRole deserialize(Automation.DeviceRole deviceRole) {
        final var deviceRoleId = deserialize(deviceRole.getDevRoleId());
        final var deviceRoleType = deserialize(deviceRole.getDevRoleType());

        return new DeviceRole(deviceRoleId, deviceRoleType);
    }

    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.Timestamp serialize(double timestamp) {
        final var builder = ContextOuterClass.Timestamp.newBuilder();

        builder.setTimestamp(timestamp);

        return builder.build();
    }

    public double deserialize(ContextOuterClass.Timestamp serializedTimeStamp) {

        return serializedTimeStamp.getTimestamp();
    }

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

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

        return builder.build();
    }

    public Event deserialize(ContextOuterClass.Event serializedEvent) {
        final var timestamp = deserialize(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();
        final var deviceIdUuid = serializeUuid(deviceEvent.getDeviceId());
        final var deviceId = DeviceId.newBuilder().setDeviceUuid(deviceIdUuid);

        builder.setDeviceId(deviceId);
        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 ContextId serializeContextId(String expectedContextId) {
        final var builder = ContextId.newBuilder();
        final var uuid = serializeUuid(expectedContextId);

        builder.setContextUuid(uuid);

        return builder.build();
    }

    public String deserialize(ContextId contextId) {
        return contextId.getContextUuid().getUuid();
    }

    public ContextOuterClass.TopologyId serialize(TopologyId topologyId) {
        final var builder = ContextOuterClass.TopologyId.newBuilder();

        final var topologyIdContextId = topologyId.getContextId();
        final var topologyIdId = topologyId.getId();

        final var contextId = serializeContextId(topologyIdContextId);
        final var topologyIdIdUuid = serializeUuid(topologyIdId);

        builder.setContextId(contextId);
        builder.setTopologyUuid(topologyIdIdUuid);

        return builder.build();
    }

    public TopologyId deserialize(ContextOuterClass.TopologyId topologyId) {
        final var topologyIdContextId = deserialize(topologyId.getContextId());
        final var topologyIdId = deserialize(topologyId.getTopologyUuid());

        return new TopologyId(topologyIdContextId, topologyIdId);
    }

    public ContextOuterClass.EndPointId serialize(EndPointId endPointId) {
        final var builder = ContextOuterClass.EndPointId.newBuilder();

        final var endPointIdTopologyId = endPointId.getTopologyId();
        final var endPointIdDeviceId = endPointId.getDeviceId();
        final var endPointIdId = endPointId.getId();

        final var serializedTopologyId = serialize(endPointIdTopologyId);
        final var serializedDeviceId = serializeDeviceId(endPointIdDeviceId);
        final var serializedEndPointIdId = serializeUuid(endPointIdId);

        builder.setTopologyId(serializedTopologyId);
        builder.setDeviceId(serializedDeviceId);
        builder.setEndpointUuid(serializedEndPointIdId);

        return builder.build();
    }

    public EndPointId deserialize(ContextOuterClass.EndPointId serializedEndPointId) {
        final var serializedTopologyId = serializedEndPointId.getTopologyId();
        final var serializedDeviceId = serializedEndPointId.getDeviceId();
        final var serializedId = serializedEndPointId.getEndpointUuid();

        final var topologyId = deserialize(serializedTopologyId);
        final var deviceId = deserialize(serializedDeviceId);
        final var id = deserialize(serializedId);

        return new EndPointId(topologyId, deviceId, id);
    }

    public Acl.AclRuleTypeEnum serialize(AclRuleTypeEnum aclRuleTypeEnum) {
        switch (aclRuleTypeEnum) {
            case IPV4:
                return Acl.AclRuleTypeEnum.ACLRULETYPE_IPV4;
            case IPV6:
                return Acl.AclRuleTypeEnum.ACLRULETYPE_IPV6;
            case L2:
                return Acl.AclRuleTypeEnum.ACLRULETYPE_L2;
            case MPLS:
                return Acl.AclRuleTypeEnum.ACLRULETYPE_MPLS;
            case MIXED:
                return Acl.AclRuleTypeEnum.ACLRULETYPE_MIXED;
            case UNDEFINED:
                return Acl.AclRuleTypeEnum.ACLRULETYPE_UNDEFINED;
            default:
                return Acl.AclRuleTypeEnum.UNRECOGNIZED;
        }
    }

    public AclRuleTypeEnum deserialize(Acl.AclRuleTypeEnum serializedAclRuleTypeEnum) {
        switch (serializedAclRuleTypeEnum) {
            case ACLRULETYPE_IPV4:
                return AclRuleTypeEnum.IPV4;
            case ACLRULETYPE_IPV6:
                return AclRuleTypeEnum.IPV6;
            case ACLRULETYPE_L2:
                return AclRuleTypeEnum.L2;
            case ACLRULETYPE_MPLS:
                return AclRuleTypeEnum.MPLS;
            case ACLRULETYPE_MIXED:
                return AclRuleTypeEnum.MIXED;
            case UNRECOGNIZED:
            default:
                return AclRuleTypeEnum.UNDEFINED;
        }
    }

    public Acl.AclMatch serialize(AclMatch aclMatch) {
        final var builder = Acl.AclMatch.newBuilder();

        final var dscp = aclMatch.getDscp();
        final var protocol = aclMatch.getProtocol();
        final var srcAddress = aclMatch.getSrcAddress();
        final var dstAddress = aclMatch.getDstAddress();
        final var srcPort = aclMatch.getSrcPort();
        final var dstPort = aclMatch.getDstPort();
        final var startMplsLabel = aclMatch.getStartMplsLabel();
        final var endMplsLabel = aclMatch.getEndMplsLabel();

        builder.setDscp(dscp);
        builder.setProtocol(protocol);
        builder.setSrcAddress(srcAddress);
        builder.setDstAddress(dstAddress);
        builder.setSrcPort(srcPort);
        builder.setDstPort(dstPort);
        builder.setStartMplsLabel(startMplsLabel);
        builder.setEndMplsLabel(endMplsLabel);

        return builder.build();
    }

    public AclMatch deserialize(Acl.AclMatch serializedAclMatch) {
        final var dscp = serializedAclMatch.getDscp();
        final var protocol = serializedAclMatch.getProtocol();
        final var srcAddress = serializedAclMatch.getSrcAddress();
        final var dstAddress = serializedAclMatch.getDstAddress();
        final var srcPort = serializedAclMatch.getSrcPort();
        final var dstPort = serializedAclMatch.getDstPort();
        final var startMplsLabel = serializedAclMatch.getStartMplsLabel();
        final var endMplsLabel = serializedAclMatch.getEndMplsLabel();

        return new AclMatch(
                dscp, protocol, srcAddress, dstAddress, srcPort, dstPort, startMplsLabel, endMplsLabel);
    }

    public Acl.AclForwardActionEnum serialize(AclForwardActionEnum aclForwardActionEnum) {
        switch (aclForwardActionEnum) {
            case DROP:
                return Acl.AclForwardActionEnum.ACLFORWARDINGACTION_DROP;
            case ACCEPT:
                return Acl.AclForwardActionEnum.ACLFORWARDINGACTION_ACCEPT;
            case REJECT:
                return Acl.AclForwardActionEnum.ACLFORWARDINGACTION_REJECT;
            case UNDEFINED:
                return Acl.AclForwardActionEnum.ACLFORWARDINGACTION_UNDEFINED;
            default:
                return Acl.AclForwardActionEnum.UNRECOGNIZED;
        }
    }

    public AclForwardActionEnum deserialize(Acl.AclForwardActionEnum serializedAclForwardActionEnum) {
        switch (serializedAclForwardActionEnum) {
            case ACLFORWARDINGACTION_DROP:
                return AclForwardActionEnum.DROP;
            case ACLFORWARDINGACTION_ACCEPT:
                return AclForwardActionEnum.ACCEPT;
            case ACLFORWARDINGACTION_REJECT:
                return AclForwardActionEnum.REJECT;
            case UNRECOGNIZED:
            default:
                return AclForwardActionEnum.UNDEFINED;
        }
    }

    public Acl.AclLogActionEnum serialize(AclLogActionEnum aclLogActionEnum) {
        switch (aclLogActionEnum) {
            case NO_LOG:
                return Acl.AclLogActionEnum.ACLLOGACTION_NOLOG;
            case SYSLOG:
                return Acl.AclLogActionEnum.ACLLOGACTION_SYSLOG;
            case UNDEFINED:
                return Acl.AclLogActionEnum.ACLLOGACTION_UNDEFINED;
            default:
                return Acl.AclLogActionEnum.UNRECOGNIZED;
        }
    }

    public AclLogActionEnum deserialize(Acl.AclLogActionEnum serializedAclLogActionEnum) {
        switch (serializedAclLogActionEnum) {
            case ACLLOGACTION_NOLOG:
                return AclLogActionEnum.NO_LOG;
            case ACLLOGACTION_SYSLOG:
                return AclLogActionEnum.SYSLOG;
            case UNRECOGNIZED:
            default:
                return AclLogActionEnum.UNDEFINED;
        }
    }

    public Acl.AclAction serialize(AclAction aclAction) {
        final var builder = Acl.AclAction.newBuilder();

        final var aclForwardActionEnum = aclAction.getAclForwardActionEnum();
        final var aclLogActionEnum = aclAction.getAclLogActionEnum();

        final var serializedAclForwardActionEnum = serialize(aclForwardActionEnum);
        final var serializedAclLogActionEnum = serialize(aclLogActionEnum);

        builder.setForwardAction(serializedAclForwardActionEnum);
        builder.setLogAction(serializedAclLogActionEnum);

        return builder.build();
    }

    public AclAction deserialize(Acl.AclAction serializedAclAction) {
        final var serializedAclForwardActionEnum = serializedAclAction.getForwardAction();
        final var serializedAclLogActionEnum = serializedAclAction.getLogAction();

        final var aclForwardActionEnum = deserialize(serializedAclForwardActionEnum);
        final var aclLogActionEnum = deserialize(serializedAclLogActionEnum);

        return new AclAction(aclForwardActionEnum, aclLogActionEnum);
    }

    public Acl.AclEntry serialize(AclEntry aclEntry) {
        final var builder = Acl.AclEntry.newBuilder();

        final var sequenceId = aclEntry.getSequenceId();
        final var description = aclEntry.getDescription();
        final var aclMatch = aclEntry.getMatch();
        final var aclAction = aclEntry.getAction();

        final var serializedAclMatch = serialize(aclMatch);
        final var serializedAclAction = serialize(aclAction);

        builder.setSequenceId(sequenceId);
        builder.setDescription(description);
        builder.setMatch(serializedAclMatch);
        builder.setAction(serializedAclAction);

        return builder.build();
    }

    public AclEntry deserialize(Acl.AclEntry serializedAclEntry) {
        final var sequenceId = serializedAclEntry.getSequenceId();
        final var description = serializedAclEntry.getDescription();
        final var serializedAclMatch = serializedAclEntry.getMatch();
        final var serializedAclAction = serializedAclEntry.getAction();

        final var aclMatch = deserialize(serializedAclMatch);
        final var aclAction = deserialize(serializedAclAction);

        return new AclEntry(sequenceId, description, aclMatch, aclAction);
    }

    public Acl.AclRuleSet serialize(AclRuleSet aclRuleSet) {
        final var builder = Acl.AclRuleSet.newBuilder();

        final var name = aclRuleSet.getName();
        final var type = aclRuleSet.getType();
        final var description = aclRuleSet.getDescription();
        final var userId = aclRuleSet.getUserId();
        final var entries = aclRuleSet.getEntries();

        final var serializedType = serialize(type);
        final var serializedEntries =
                entries.stream().map(this::serialize).collect(Collectors.toList());

        builder.setName(name);
        builder.setType(serializedType);
        builder.setDescription(description);
        builder.setUserId(userId);
        builder.addAllEntries(serializedEntries);

        return builder.build();
    }

    public AclRuleSet deserialize(Acl.AclRuleSet serializedAclRuleSet) {
        final var serializedName = serializedAclRuleSet.getName();
        final var serializedType = serializedAclRuleSet.getType();
        final var serializedDescription = serializedAclRuleSet.getDescription();
        final var serializedUserId = serializedAclRuleSet.getUserId();
        final var serializedEntries = serializedAclRuleSet.getEntriesList();

        final var type = deserialize(serializedType);
        final var entries =
                serializedEntries.stream().map(this::deserialize).collect(Collectors.toList());

        return new AclRuleSet(serializedName, type, serializedDescription, serializedUserId, entries);
    }

    public ConfigRule_ACL serialize(ConfigRuleAcl configRuleAcl) {
        final var builder = ContextOuterClass.ConfigRule_ACL.newBuilder();

        final var endPointId = configRuleAcl.getEndPointId();
        final var aclRuleSet = configRuleAcl.getRuleSet();

        final var serializedEndPointId = serialize(endPointId);
        final var serializedAclRuleSet = serialize(aclRuleSet);

        builder.setEndpointId(serializedEndPointId);
        builder.setRuleSet(serializedAclRuleSet);

        return builder.build();
    }

    public ConfigRuleAcl deserialize(ConfigRule_ACL serializedConfigRuleAcl) {
        final var serializedEndPointId = serializedConfigRuleAcl.getEndpointId();
        final var serializedAclRuleSet = serializedConfigRuleAcl.getRuleSet();

        final var endPointId = deserialize(serializedEndPointId);
        final var aclRuleSet = deserialize(serializedAclRuleSet);

        return new ConfigRuleAcl(endPointId, aclRuleSet);
    }

    public ConfigRule_Custom serialize(ConfigRuleCustom configRuleCustom) {
        final var builder = ConfigRule_Custom.newBuilder();

        final var resourceKey = configRuleCustom.getResourceKey();
        final var resourceValue = configRuleCustom.getResourceValue();

        builder.setResourceKey(resourceKey);
        builder.setResourceValue(resourceValue);

        return builder.build();
    }

    public ConfigRuleCustom deserialize(ConfigRule_Custom serializedConfigRuleCustom) {
        final var serializedResourceKey = serializedConfigRuleCustom.getResourceKey();
        final var serializedResourceValue = serializedConfigRuleCustom.getResourceValue();

        return new ConfigRuleCustom(serializedResourceKey, serializedResourceValue);
    }

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

        final var configActionEnum = configRule.getConfigActionEnum();
        final var configRuleType = configRule.getConfigRuleType();
        final var configRuleTypeSpecificType = configRuleType.getConfigRuleType();

        if (configRuleTypeSpecificType instanceof ConfigRuleAcl) {
            final var endPointId = ((ConfigRuleAcl) configRuleTypeSpecificType).getEndPointId();
            final var aclRuleSet = ((ConfigRuleAcl) configRuleTypeSpecificType).getRuleSet();

            final var serializedEndPointId = serialize(endPointId);
            final var serializedAclRuleSet = serialize(aclRuleSet);

            final var serializedConfigRuleAcl =
                    ConfigRule_ACL.newBuilder()
                            .setEndpointId(serializedEndPointId)
                            .setRuleSet(serializedAclRuleSet)
                            .build();

            builder.setAcl(serializedConfigRuleAcl);
        }

        if (configRuleTypeSpecificType instanceof ConfigRuleCustom) {
            final var configRuleCustomResourceKey =
                    ((ConfigRuleCustom) configRuleTypeSpecificType).getResourceKey();
            final var configRuleCustomResourceValue =
                    ((ConfigRuleCustom) configRuleTypeSpecificType).getResourceValue();

            final var serializedConfigRuleCustom =
                    ConfigRule_Custom.newBuilder()
                            .setResourceKey(configRuleCustomResourceKey)
                            .setResourceValue(configRuleCustomResourceValue)
                            .build();

            builder.setCustom(serializedConfigRuleCustom);
        }

        final var serializedConfigActionEnum = serialize(configActionEnum);

        builder.setAction(serializedConfigActionEnum);

        return builder.build();
    }

    public ConfigRule deserialize(ContextOuterClass.ConfigRule serializedConfigRule) {
        final var serializedConfigActionEnum = serializedConfigRule.getAction();
        final var typeOfConfigRule = serializedConfigRule.getConfigRuleCase();

        final var configActionEnum = deserialize(serializedConfigActionEnum);

        switch (typeOfConfigRule) {
            case ACL:
                final var serializedConfigRuleAcl = serializedConfigRule.getAcl();

                final var configRuleAcl = deserialize(serializedConfigRuleAcl);
                final var configRuleTypeAcl = new ConfigRuleTypeAcl(configRuleAcl);

                return new ConfigRule(configActionEnum, configRuleTypeAcl);
            case CUSTOM:
                final var serializedConfigRuleCustom = serializedConfigRule.getCustom();

                final var configRuleCustom = deserialize(serializedConfigRuleCustom);
                final var configRuleTypeCustom = new ConfigRuleTypeCustom(configRuleCustom);

                return new ConfigRule(configActionEnum, configRuleTypeCustom);
            default:
            case CONFIGRULE_NOT_SET:
                throw new IllegalStateException("Config Rule not set");
        }
    }

    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();
        final var deviceIdUuid = serializeUuid(device.getDeviceId());
        final var deviceId = DeviceId.newBuilder().setDeviceUuid(deviceIdUuid);

        builder.setDeviceId(deviceId);
        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();
    }
}
