/*
 * 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 automation.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 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.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();
        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 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();
        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();
    }
}
