// Copyright (c) 2020 UWB & LESP.
// The UWB & LESP license this file to you under the MIT license.

package cz.senslog.connector.model.converter;

import cz.senslog.connector.model.api.Converter;
import cz.senslog.connector.model.fieldclimate.FieldClimateModel;
import cz.senslog.connector.model.fieldclimate.SensorDataInfo;
import cz.senslog.connector.model.fieldclimate.SensorType;
import cz.senslog.connector.model.fieldclimate.StationData;
import cz.senslog.connector.model.v1.Observation;
import cz.senslog.connector.model.v1.Position;
import cz.senslog.connector.model.v1.Record;
import cz.senslog.connector.model.v1.SenslogV1Model;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static cz.senslog.connector.model.fieldclimate.SensorType.Group.POSITION;
import static cz.senslog.connector.model.fieldclimate.SensorType.countOfGroup;
import static java.lang.String.format;
import static java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME;

public class FieldClimateModelSenslogV1ModelConverter implements Converter<FieldClimateModel, SenslogV1Model> {

    private static final Logger logger = LogManager.getLogger(FieldClimateModelSenslogV1ModelConverter.class);

    @Override
    public SenslogV1Model convert(FieldClimateModel model) {

        if (model == null || model.getStations() == null) {
            logger.warn("Nothing to convert. Received model is empty.");
            return null;
        }

        List<StationData> stations = model.getStations();

        int size = 0;
        for (StationData sensor : stations) {
            for (Map<String, String> sensorData : sensor.getData()) {
                size += sensorData.size();
            }
        }

        List<Record> observations = new ArrayList<>(size);
        for (StationData station : stations) {

            int locationMapInitSize = station.getData().size() * countOfGroup(POSITION);
            Map<OffsetDateTime, Position> locationData = new HashMap<>(locationMapInitSize);

            Long unitId = FieldClimateUnitConverter.convertIdToSensLogId(station.getId());

            for (SensorDataInfo sensor : station.getSensors()) {

                SensorType sensorType = SensorType.of(sensor.getCode());

                if (sensorType == null) {
                    logger.warn("Sensor type is not registered: {}", sensor); continue;
                }

                Map<String, Integer> aggregateInfos = sensor.getAggr();

                if (aggregateInfos.size() != 1) {
                    Integer avgActive = aggregateInfos.getOrDefault("avg", 0);
                    if (avgActive == 1) {
                        aggregateInfos.clear();
                        aggregateInfos.put("avg", 1);
                    } else {
                        Map.Entry<String, Integer> entry = null;
                        for (Map.Entry<String, Integer> aggrEntry : aggregateInfos.entrySet()) {
                            if (aggrEntry.getValue() == 1) {
                                entry = aggrEntry;
                                break;
                            }
                        }
                        aggregateInfos.clear();
                        if (entry != null) {
                            aggregateInfos.put(entry.getKey(), entry.getValue());
                        }
                    }
                }

                for (Map.Entry<String, Integer> aggrEntry : aggregateInfos.entrySet()) {

                    String aggrType = aggrEntry.getKey();
                    String sensorDataHash = format("%s_%s_%s_%s_%s",
                            sensor.getCh(), sensor.getMac(), sensor.getSerial(), sensor.getCode(), aggrType);

                    for (Map<String, String> dataMap : station.getData()) {

                        if (!dataMap.containsKey(sensorDataHash)) {
                            logger.warn("For the sensor {}({}) at the station {} does not exist aggregate '{}' value.",
                                    sensor.getName(), sensor.getCode(), station.getId(), aggrType);
                            continue;
                        }

                        String strValue = dataMap.get(sensorDataHash);

                        if (strValue == null) continue;

                        Float value = Float.valueOf(strValue);
                        String strDate = dataMap.getOrDefault("date", "");
                        OffsetDateTime time = OffsetDateTime.parse(strDate, ISO_OFFSET_DATE_TIME);

                        if (sensorType.isGrouped()) {
                            switch (sensorType.getGroup()) {
                                case POSITION: {
                                    Position position = locationData.getOrDefault(time, null);
                                    if (position == null) {
                                        position = new Position();
                                        position.setUnitId(unitId);
                                        position.setTime(time);
                                        locationData.put(time, position);
                                    }

                                    switch (sensorType) {
                                        case LATITUDE:  position.setLatitude(value);                        break;
                                        case LONGITUDE: position.setLongitude(value);                       break;
                                        case ALTITUDE:  position.setAltitude(value);                        break;
                                        case HDOP:      position.setDilutionOfPrecision(value.intValue());  break;
                                    }
                                } break;
                                case MULTICHANNEL: {
                                    Long sensorId = sensorType.getId() + sensor.getCh();
                                    Observation observation = new Observation();
                                    observation.setTime(time);
                                    observation.setUnitId(unitId);
                                    observation.setSensorId(sensorId);
                                    observation.setValue(value);

                                    observations.add(observation);
                                } break;
                            }
                        } else {
                            Observation observation = new Observation();
                            observation.setTime(time);
                            observation.setUnitId(unitId);
                            observation.setSensorId(sensorType.getId());
                            observation.setValue(value);

                            observations.add(observation);
                        }
                    }
                }
            }

            observations.addAll(locationData.values());
        }

        return new SenslogV1Model(observations, model.getFrom(), model.getTo());
    }
}