package org.skywalking.apm.trace;

import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
import org.skywalking.apm.trace.TraceId.DistributedTraceId;
import org.skywalking.apm.trace.TraceId.DistributedTraceIds;
import org.skywalking.apm.trace.TraceId.NewDistributedTraceId;

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

/**
 * {@link TraceSegment} is a segment or fragment of the distributed trace.
 * {@see https://github.com/opentracing/specification/blob/master/specification.md#the-opentracing-data-model}
 * A {@link
 * TraceSegment} means the segment, which exists in current {@link Thread}. And the distributed trace is formed by multi
 * {@link TraceSegment}s, because the distributed trace crosses multi-processes, multi-threads.
 * <p>
 * Created by wusheng on 2017/2/17.
 */
public class TraceSegment {
    private static final String ID_TYPE = "Segment";

    /**
     * The id of this trace segment.
     * Every segment has its unique-global-id.
     */
    @Expose
    @SerializedName(value = "ts")
    private String traceSegmentId;

    /**
     * The start time of this trace segment.
     */
    @Expose
    @SerializedName(value = "st")
    private long startTime;

    /**
     * The end time of this trace segment.
     */
    @Expose
    @SerializedName(value = "et")
    private long endTime;

    /**
     * The refs of parent trace segments, except the primary one.
     * For most RPC call, {@link #refs} contains only one element,
     * but if this segment is a start span of batch process, the segment faces multi parents,
     * at this moment, we use this {@link #refs} to link them.
     */
    @Expose
    @SerializedName(value = "rs")
    private List<TraceSegmentRef> refs;

    /**
     * The spans belong to this trace segment.
     * They all have finished.
     * All active spans are hold and controlled by "skywalking-api" module.
     */
    @Expose
    @SerializedName(value = "ss")
    private List<Span> spans;

    /**
     * The <code>applicationCode</code> represents a name of current application/JVM and indicates which is business
     * role in the cluster.
     * <p>
     * e.g. account_app, billing_app
     */
    @Expose
    @SerializedName(value = "ac")
    private String applicationCode;

    /**
     * The <code>relatedGlobalTraces</code> represent a set of all related trace. Most time it contains only one
     * element, because only one parent {@link TraceSegment} exists, but, in batch scenario, the num becomes greater
     * than 1, also meaning multi-parents {@link TraceSegment}.
     * <p>
     * The difference between <code>relatedGlobalTraces</code> and {@link #refs} is:
     * {@link #refs} targets this {@link TraceSegment}'s direct parent,
     * <p>
     * and
     * <p>
     * <code>relatedGlobalTraces</code> targets this {@link TraceSegment}'s related call chain, a call chain contains
     * multi {@link TraceSegment}s, only using {@link #refs} is not enough for analysis and ui.
     */
    @Expose
    @SerializedName(value = "gt")
    private DistributedTraceIds relatedGlobalTraces;

    /**
     * The <code>sampled</code> is a flag, which represent, when this {@link TraceSegment} finished, it need to be send
     * to Collector.
     * <p>
     * Its value depends on SamplingService. True, by default.
     * <p>
     * This value is not serialized.
     */
    private boolean sampled;

    /**
     * Create a trace segment, by the given applicationCode.
     */
    public TraceSegment(String applicationCode) {
        this();
        this.applicationCode = applicationCode;
    }

    /**
     * Create a default/empty trace segment,
     * with current time as start time,
     * and generate a new segment id.
     */
    public TraceSegment() {
        this.startTime = System.currentTimeMillis();
        this.traceSegmentId = GlobalIdGenerator.generate(ID_TYPE);
        this.spans = new LinkedList<Span>();
        this.relatedGlobalTraces = new DistributedTraceIds();
        this.relatedGlobalTraces.append(new NewDistributedTraceId());
        this.sampled = true;
    }

    /**
     * Establish the link between this segment and its parents.
     *
     * @param refSegment {@link TraceSegmentRef}
     */
    public void ref(TraceSegmentRef refSegment) {
        if (refs == null) {
            refs = new LinkedList<TraceSegmentRef>();
        }
        if (!refs.contains(refSegment)) {
            refs.add(refSegment);
        }
    }

    /**
     * Establish the line between this segment and all relative global trace ids.
     *
     * @param distributedTraceIds multi global trace ids. @see {@link DistributedTraceId}
     */
    public void relatedGlobalTraces(List<DistributedTraceId> distributedTraceIds) {
        if (distributedTraceIds == null || distributedTraceIds.size() == 0) {
            return;
        }
        for (DistributedTraceId distributedTraceId : distributedTraceIds) {
            relatedGlobalTraces.append(distributedTraceId);
        }
    }

    /**
     * After {@link Span} is finished, as be controller by "skywalking-api" module,
     * notify the {@link TraceSegment} to archive it.
     *
     * @param finishedSpan
     */
    public void archive(Span finishedSpan) {
        spans.add(finishedSpan);
    }

    /**
     * Finish this {@link TraceSegment}.
     * <p>
     * return this, for chaining
     */
    public TraceSegment finish() {
        this.endTime = System.currentTimeMillis();
        return this;
    }

    public String getTraceSegmentId() {
        return traceSegmentId;
    }

    public long getStartTime() {
        return startTime;
    }

    public long getEndTime() {
        return endTime;
    }

    public List<TraceSegmentRef> getRefs() {
        if (refs == null) {
            return null;
        }
        return Collections.unmodifiableList(refs);
    }

    public List<DistributedTraceId> getRelatedGlobalTraces() {
        return relatedGlobalTraces.getRelatedGlobalTraces();
    }

    public List<Span> getSpans() {
        return Collections.unmodifiableList(spans);
    }

    public String getApplicationCode() {
        return applicationCode;
    }

    public boolean isSampled() {
        return sampled;
    }

    public void setSampled(boolean sampled) {
        this.sampled = sampled;
    }

    @Override
    public String toString() {
        return "TraceSegment{" +
            "traceSegmentId='" + traceSegmentId + '\'' +
            ", startTime=" + startTime +
            ", endTime=" + endTime +
            ", refs=" + refs +
            ", spans=" + spans +
            ", applicationCode='" + applicationCode + '\'' +
            ", relatedGlobalTraces=" + relatedGlobalTraces +
            '}';
    }
}
