/*
 * Copyright 2017 NAVER Corp.
 *
 * 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 com.navercorp.pinpoint.web.mapper.stat.sampling.sampler;

import com.navercorp.pinpoint.common.server.bo.JvmGcType;
import com.navercorp.pinpoint.common.server.bo.stat.JvmGcBo;
import com.navercorp.pinpoint.web.vo.stat.SampledJvmGc;
import com.navercorp.pinpoint.web.vo.stat.chart.agent.AgentStatPoint;
import com.navercorp.pinpoint.web.vo.stat.chart.agent.AgentStatPointSummary;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

/**
 * @author HyunGil Jeong
 */
@Component
public class JvmGcSampler implements AgentStatSampler<JvmGcBo, SampledJvmGc> {

    @Override
    public SampledJvmGc sampleDataPoints(int timeWindowIndex, long timestamp, List<JvmGcBo> dataPoints, JvmGcBo previousDataPoint) {
        JvmGcType jvmGcType = JvmGcType.UNKNOWN;
        List<Long> heapUseds = new ArrayList<>(dataPoints.size());
        List<Long> heapMaxes = new ArrayList<>(dataPoints.size());
        List<Long> nonHeapUseds = new ArrayList<>(dataPoints.size());
        List<Long> nonHeapMaxes = new ArrayList<>(dataPoints.size());
        List<Long> gcOldCounts = new ArrayList<>(dataPoints.size());
        List<Long> gcOldTimes = new ArrayList<>(dataPoints.size());
        // dataPoints are in descending order
        JvmGcBo previousBo = previousDataPoint;
        for (int i = dataPoints.size() - 1; i >= 0; --i) {
            JvmGcBo jvmGcBo = dataPoints.get(i);
            jvmGcType = jvmGcBo.getGcType();
            if (jvmGcBo.getHeapUsed() != JvmGcBo.UNCOLLECTED_VALUE) {
                heapUseds.add(jvmGcBo.getHeapUsed());
            }
            if (jvmGcBo.getHeapMax() != JvmGcBo.UNCOLLECTED_VALUE) {
                heapMaxes.add(jvmGcBo.getHeapMax());
            }
            if (jvmGcBo.getNonHeapUsed() != JvmGcBo.UNCOLLECTED_VALUE) {
                nonHeapUseds.add(jvmGcBo.getNonHeapUsed());
            }
            if (jvmGcBo.getNonHeapMax() != JvmGcBo.UNCOLLECTED_VALUE) {
                nonHeapMaxes.add(jvmGcBo.getNonHeapMax());
            }

            if (previousBo != null) {
                // Technically, this should not be needed as data should already be partitioned by their agent start
                // timestamp and should only contain data from a single jvm life cycle.
                // Added to maintain backwards compatibility for data that do not have agent start timestamp.
                if (checkJvmRestart(previousBo, jvmGcBo)) {
                    if (isGcCollected(jvmGcBo)) {
                        gcOldCounts.add(jvmGcBo.getGcOldCount());
                        gcOldTimes.add(jvmGcBo.getGcOldTime());
                    } else {
                        jvmGcBo.setGcOldCount(0L);
                        jvmGcBo.setGcOldTime(0L);
                    }
                } else {
                    if (isGcCollected(jvmGcBo) && isGcCollected(previousBo)) {
                        gcOldCounts.add(jvmGcBo.getGcOldCount() - previousBo.getGcOldCount());
                        gcOldTimes.add(jvmGcBo.getGcOldTime() - previousBo.getGcOldTime());
                    } else {
                        if (!isGcCollected(jvmGcBo)) {
                            jvmGcBo.setGcOldCount(previousBo.getGcOldCount());
                            jvmGcBo.setGcOldTime(previousBo.getGcOldTime());
                        }
                    }
                }
            } else {
                if (isGcCollected(jvmGcBo)) {
                    if (timeWindowIndex > 0) {
                        gcOldCounts.add(jvmGcBo.getGcOldCount());
                        gcOldTimes.add(jvmGcBo.getGcOldTime());
                    } else {
                        gcOldCounts.add(0L);
                        gcOldTimes.add(0L);
                    }
                }
            }
            previousBo = jvmGcBo;
        }
        SampledJvmGc sampledJvmGc = new SampledJvmGc();
        sampledJvmGc.setJvmGcType(jvmGcType);
        sampledJvmGc.setHeapUsed(createSampledPoint(timestamp, heapUseds));
        sampledJvmGc.setHeapMax(createSampledPoint(timestamp, heapMaxes));
        sampledJvmGc.setNonHeapUsed(createSampledPoint(timestamp, nonHeapUseds));
        sampledJvmGc.setNonHeapMax(createSampledPoint(timestamp, nonHeapMaxes));
        sampledJvmGc.setGcOldCount(createSampledPoint(timestamp, gcOldCounts));
        sampledJvmGc.setGcOldTime(createSampledPoint(timestamp, gcOldTimes));
        return sampledJvmGc;
    }

    private boolean isGcCollected(JvmGcBo jvmGcBo) {
        return jvmGcBo.getGcOldCount() != JvmGcBo.UNCOLLECTED_VALUE && jvmGcBo.getGcOldTime() != JvmGcBo.UNCOLLECTED_VALUE;
    }

    private boolean checkJvmRestart(JvmGcBo previous, JvmGcBo current) {
        if (previous.getStartTimestamp() > 0 && current.getStartTimestamp() > 0) {
            return previous.getStartTimestamp() != current.getStartTimestamp();
        } else {
            // if start timestamp is not serialzied
            if (current.getGcOldTime() == JvmGcBo.UNCOLLECTED_VALUE || current.getGcOldCount() == JvmGcBo.UNCOLLECTED_VALUE) {
                return false;
            } else {
                long countDelta = current.getGcOldCount() - previous.getGcOldCount();
                long timeDelta = current.getGcOldTime() - previous.getGcOldTime();
                return countDelta < 0 && timeDelta < 0;
            }
        }
    }

    private AgentStatPoint<Long> createSampledPoint(long timestamp, List<Long> values) {
        if (CollectionUtils.isEmpty(values)) {
            return SampledJvmGc.UNCOLLECTED_POINT_CREATOR.createUnCollectedPoint(timestamp);
        }
        return AgentStatPointSummary.longSummary(timestamp, values);
    }
}
