package com.njcn.gather.tool.parsepqdif.reader;

import com.njcn.gather.tool.parsepqdif.nativebridge.PqdifNativeLibraryLoader;
import com.njcn.gather.tool.parsepqdif.pojo.vo.PqdifParseResponse;
import com.njcn.pqdif.nativebridge.PqdifBasicNative;
import com.njcn.pqdif.nativebridge.PqdifNativeSession;
import org.springframework.stereotype.Component;

import java.nio.file.Path;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

@Component
public class PqdifNativeReader {

    private static final String STATUS_SUCCESS = "SUCCESS";
    private static final String DATA_SUCCESS = "DATA_SUCCESS";
    private static final String DATA_FAILED = "DATA_FAILED";
    private static final int DEFAULT_SAMPLE_VALUE_COUNT = 5;

    public PqdifParseResponse read(Path pqdifPath, String fileName) {
        PqdifNativeLibraryLoader.ensurePrepared();

        PqdifParseResponse response = new PqdifParseResponse();
        response.setStatus(STATUS_SUCCESS);
        response.setMessage("PQDIF解析完成");
        response.setFileName(fileName);
        response.setNativeVersion(PqdifBasicNative.INSTANCE.pqdif_basic_version());
        response.setSampleValueCount(DEFAULT_SAMPLE_VALUE_COUNT);
        response.setRecords(new ArrayList<PqdifParseResponse.RecordInfoVO>());
        response.setObservations(new ArrayList<PqdifParseResponse.ObservationVO>());

        try (PqdifNativeSession session = new PqdifNativeSession()) {
            session.open(pqdifPath.toAbsolutePath().toString());

            long recordCount = session.getRecordCount();
            response.setRecordCount(recordCount);

            for (long recordIndex = 0; recordIndex < recordCount; recordIndex++) {
                PqdifNativeSession.RecordInfo recordInfo = session.getRecordInfo(recordIndex);

                PqdifParseResponse.RecordInfoVO recordVO = new PqdifParseResponse.RecordInfoVO();
                recordVO.setRecordIndex(recordIndex);
                recordVO.setTypeGuid(recordInfo.typeGuid);
                recordVO.setTypeName(recordInfo.typeName);
                boolean observationRecord = isObservation(recordInfo);
                recordVO.setObservation(observationRecord);
                response.getRecords().add(recordVO);

                if (!observationRecord) {
                    continue;
                }

                try (PqdifNativeSession.Observation observation = session.openObservation(recordIndex)) {
                    response.getObservations().add(readObservation(recordIndex, observation));
                }
            }
        }

        response.setObservationCount((long) response.getObservations().size());
        return response;
    }

    private PqdifParseResponse.ObservationVO readObservation(
            long recordIndex,
            PqdifNativeSession.Observation observation) {

        PqdifNativeSession.ObservationInfo info = observation.getInfo();

        PqdifParseResponse.ObservationVO vo = new PqdifParseResponse.ObservationVO();
        vo.setRecordIndex(recordIndex);
        vo.setName(info.name);
        vo.setTimeStartExcelDays(info.timeStartExcelDays);
        vo.setTimeStartText(toExcelDateTimeText(info.timeStartExcelDays));
        vo.setChannelCount(info.channelCount);
        vo.setChannels(new ArrayList<PqdifParseResponse.ChannelInfoVO>());

        for (long channelIndex = 0; channelIndex < info.channelCount; channelIndex++) {
            PqdifNativeSession.ChannelInfo channelInfo = observation.getChannelInfo(channelIndex);
            vo.getChannels().add(readChannel(observation, channelIndex, channelInfo));
        }

        return vo;
    }

    private PqdifParseResponse.ChannelInfoVO readChannel(
            PqdifNativeSession.Observation observation,
            long channelIndex,
            PqdifNativeSession.ChannelInfo channelInfo) {

        PqdifParseResponse.ChannelInfoVO vo = new PqdifParseResponse.ChannelInfoVO();
        vo.setChannelIndex(channelIndex);
        vo.setName(channelInfo.name);
        vo.setSeriesCount(channelInfo.seriesCount);
        vo.setPhaseId(channelInfo.phaseId);
        vo.setQuantityTypeGuid(channelInfo.quantityTypeGuid);
        vo.setQuantityMeasuredId(channelInfo.quantityMeasuredId);
        vo.setSeries(new ArrayList<PqdifParseResponse.SeriesInfoVO>());

        for (long seriesIndex = 0; seriesIndex < channelInfo.seriesCount; seriesIndex++) {
            vo.getSeries().add(readSeries(observation, channelIndex, seriesIndex));
        }

        return vo;
    }

    private PqdifParseResponse.SeriesInfoVO readSeries(
            PqdifNativeSession.Observation observation,
            long channelIndex,
            long seriesIndex) {

        PqdifParseResponse.SeriesInfoVO vo = new PqdifParseResponse.SeriesInfoVO();
        vo.setSeriesIndex(seriesIndex);

        PqdifNativeSession.SeriesInfo seriesInfo = null;

        try {
            seriesInfo = observation.getSeriesInfo(channelIndex, seriesIndex);

            vo.setQuantityUnitsId(seriesInfo.quantityUnitsId);
            vo.setQuantityCharacteristicGuid(seriesInfo.quantityCharacteristicGuid);
            vo.setValueTypeGuid(seriesInfo.valueTypeGuid);
            vo.setSeriesBaseType(seriesInfo.seriesBaseType);
            vo.setScale(seriesInfo.scale);
            vo.setOffset(seriesInfo.offset);
        } catch (Throwable e) {
            vo.setDataStatus(DATA_FAILED);
            vo.setDataMessage("getSeriesInfo failed, channel=" + channelIndex
                    + ", series=" + seriesIndex
                    + ", error=" + e.getMessage());
            vo.setValueCount(0);
            vo.setFirstValues(new ArrayList<Double>());
            return vo;
        }

        try {
            double[] values = observation.getSeriesData(channelIndex, seriesIndex);

            vo.setDataStatus(DATA_SUCCESS);
            vo.setDataMessage(null);
            vo.setValueCount(values == null ? 0 : values.length);
            vo.setFirstValues(firstValues(values, DEFAULT_SAMPLE_VALUE_COUNT));
        } catch (Throwable e) {
            vo.setDataStatus(DATA_FAILED);
            vo.setDataMessage("getSeriesData failed, channel=" + channelIndex
                    + ", series=" + seriesIndex
                    + ", seriesBaseType=" + vo.getSeriesBaseType()
                    + ", valueTypeGuid=" + vo.getValueTypeGuid()
                    + ", characteristicGuid=" + vo.getQuantityCharacteristicGuid()
                    + ", error=" + e.getMessage());
            vo.setValueCount(0);
            vo.setFirstValues(new ArrayList<Double>());
        }

        return vo;
    }

    private static boolean isObservation(PqdifNativeSession.RecordInfo recordInfo) {
        if (recordInfo == null || recordInfo.typeName == null) {
            return false;
        }
        String type = recordInfo.typeName.toLowerCase(Locale.ROOT);
        return type.contains("observation") || type.contains("tagrecobservation");
    }

    private static List<Double> firstValues(double[] values, int maxCount) {
        if (values == null || values.length == 0) {
            return new ArrayList<Double>();
        }

        int count = Math.min(values.length, maxCount);
        List<Double> result = new ArrayList<Double>();
        for (int i = 0; i < count; i++) {
            result.add(values[i]);
        }
        return result;
    }

    private static String toExcelDateTimeText(double excelDays) {
        long days = (long) Math.floor(excelDays);
        double dayFraction = excelDays - days;
        long nanos = Math.round(dayFraction * 24D * 60D * 60D * 1_000_000_000D);

        LocalDateTime dateTime = LocalDateTime.of(1899, 12, 30, 0, 0)
                .plusDays(days)
                .plusNanos(nanos);

        return dateTime.toString();
    }
}
