package org.skywalking.apm.agent.core.remote;

import io.grpc.ManagedChannel;
import io.grpc.stub.StreamObserver;
import java.util.List;
import org.skywalking.apm.agent.core.boot.BootService;
import org.skywalking.apm.agent.core.boot.ServiceManager;
import org.skywalking.apm.agent.core.context.TracingContext;
import org.skywalking.apm.agent.core.context.TracingContextListener;
import org.skywalking.apm.agent.core.context.trace.TraceSegment;
import org.skywalking.apm.agent.core.datacarrier.DataCarrier;
import org.skywalking.apm.agent.core.datacarrier.buffer.BufferStrategy;
import org.skywalking.apm.agent.core.datacarrier.consumer.IConsumer;
import org.skywalking.apm.logging.ILog;
import org.skywalking.apm.logging.LogManager;
import org.skywalking.apm.network.collecor.proto.Downstream;
import org.skywalking.apm.network.trace.proto.TraceSegmentServiceGrpc;
import org.skywalking.apm.network.trace.proto.UpstreamSegment;

import static org.skywalking.apm.agent.core.conf.Config.Buffer.BUFFER_SIZE;
import static org.skywalking.apm.agent.core.conf.Config.Buffer.CHANNEL_SIZE;
import static org.skywalking.apm.agent.core.remote.GRPCChannelStatus.CONNECTED;

/**
 * @author wusheng
 */
public class TraceSegmentServiceClient implements BootService, IConsumer<TraceSegment>, TracingContextListener, GRPCChannelListener {
    private static final ILog logger = LogManager.getLogger(TraceSegmentServiceClient.class);

    private volatile DataCarrier<TraceSegment> carrier;
    private volatile TraceSegmentServiceGrpc.TraceSegmentServiceStub serviceStub;
    private volatile GRPCChannelStatus status = null;

    @Override
    public void beforeBoot() throws Throwable {
        ServiceManager.INSTANCE.findService(GRPCChannelManager.class).addChannelListener(this);
    }

    @Override
    public void boot() throws Throwable {
        carrier = new DataCarrier<TraceSegment>(CHANNEL_SIZE, BUFFER_SIZE);
        carrier.setBufferStrategy(BufferStrategy.IF_POSSIBLE);
        carrier.consume(this, 1);
    }

    @Override
    public void afterBoot() throws Throwable {
        TracingContext.ListenerManager.add(this);
    }

    @Override
    public void init() {

    }

    @Override
    public void consume(List<TraceSegment> data) {
        if (CONNECTED.equals(status)) {
            final GRPCStreamServiceStatus status = new GRPCStreamServiceStatus(false);
            StreamObserver<UpstreamSegment> upstreamSegmentStreamObserver = serviceStub.collect(new StreamObserver<Downstream>() {
                @Override
                public void onNext(Downstream downstream) {

                }

                @Override
                public void onError(Throwable throwable) {
                    status.finished();
                    if (logger.isErrorEnable()) {
                        logger.error(throwable, "Send UpstreamSegment to collector fail with a grpc internal exception.");
                    }
                    ServiceManager.INSTANCE.findService(GRPCChannelManager.class).reportError(throwable);
                }

                @Override
                public void onCompleted() {
                    status.finished();
                }
            });

            try {
                for (TraceSegment segment : data) {
                    //TODO
                    // segment to PROTOBUFF object
                    upstreamSegmentStreamObserver.onNext(null);
                }
            } catch (Throwable t) {
                logger.error(t, "Send UpstreamSegment to collector fail.");
            }
            upstreamSegmentStreamObserver.onCompleted();

            status.wait4Finish(30 * 1000);

            if (logger.isDebugEnable()) {
                logger.debug("{} trace segments have been sent to collector.", data.size());
            }
        } else {
            if (logger.isDebugEnable()) {
                logger.debug("{} trace segments have been abandoned, cause by no available channel.", data.size());
            }
        }
    }

    @Override
    public void onError(List<TraceSegment> data, Throwable t) {
        logger.error(t, "Try to send {} trace segments to collector, with unexpected exception.", data.size());
    }

    @Override
    public void onExit() {

    }

    @Override
    public void afterFinished(TraceSegment traceSegment) {
        carrier.produce(traceSegment);
    }

    @Override
    public void statusChanged(GRPCChannelStatus status) {
        if (CONNECTED.equals(status)) {
            ManagedChannel channel = ServiceManager.INSTANCE.findService(GRPCChannelManager.class).getManagedChannel();
            serviceStub = TraceSegmentServiceGrpc.newStub(channel);
        } else {

        }
    }
}

@@ -4321,32 +4321,37 @@
 ment) %7B%0A        
+if (!
 carrier.produce(
@@ -4363,17 +4363,181 @@
 Segment)
-;
+) %7B%0A            if (logger.isDebugEnable()) %7B%0A                logger.debug(%22One trace segment has been abandoned, cause by buffer is full.%22);%0A            %7D%0A        %7D
 %0A    %7D%0A%0A

Add a log when the data carrier buffer is full.