package com.perimeterx.api.activities;

import com.perimeterx.http.PXClient;
import com.perimeterx.models.PXContext;
import com.perimeterx.models.activities.*;
import com.perimeterx.models.configuration.PXConfiguration;
import com.perimeterx.models.exceptions.PXException;
import com.perimeterx.utils.Constants;
import edu.emory.mathcs.backport.java.util.Arrays;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;

/**
 * Buffered activities and sends them to PX servers when buffer is full
 * <p>
 * Created by nitzangoldfeder on 05/03/2017.
 */
public class BufferedActivityHandler implements ActivityHandler {

    private final int maxBufferLength;
    private volatile ConcurrentLinkedQueue<Activity> bufferedActivities = new ConcurrentLinkedQueue<>();
    private final AtomicInteger counter = new AtomicInteger(0);
    private PXConfiguration configuration;
    private PXClient client;
    private ReentrantLock lock = new ReentrantLock();


    public BufferedActivityHandler(PXClient client, PXConfiguration configuration) {
        this.configuration = configuration;
        this.client = client;
        maxBufferLength = configuration.getMaxBufferLen();
    }

    @Override
    public void handleBlockActivity(PXContext context) throws PXException {
        Activity activity = ActivityFactory.createActivity(Constants.ACTIVITY_BLOCKED, configuration.getAppId(), context);
        handleSendActivities(activity);
    }

    @Override
    public void handlePageRequestedActivity(PXContext context) throws PXException {
        Activity activity = ActivityFactory.createActivity(Constants.ACTIVITY_PAGE_REQUESTED, configuration.getAppId(), context);
        handleSendActivities(activity);
    }

    @Override
    public void handleEnforcerTelemetryActivity(PXConfiguration pxConfig, UpdateReason updateReason) throws PXException {
        try {
            EnforcerTelemetryActivityDetails details = new EnforcerTelemetryActivityDetails(pxConfig, updateReason);
            EnforcerTelemetry enforcerTelemetry = new EnforcerTelemetry("enforcer_telemetry", pxConfig.getAppId(), details);
            this.client.sendEnforcerTelemetry(enforcerTelemetry);
        } catch (IOException e) {
            throw new PXException(e);
        }
    }

    private void handleSendActivities(Activity activity) throws PXException {
        int count = counter.incrementAndGet();
        if (count >= maxBufferLength) {
            handleOverflow();
        }
        bufferedActivities.add(activity);
    }

    private void handleOverflow() throws PXException {
        lock.lock();
        if (counter.get() >= maxBufferLength) {
            ConcurrentLinkedQueue<Activity> activitiesToSend = flush();
            lock.unlock();
            sendAsync(activitiesToSend);
        } else {    //flush already handled
            lock.unlock();
        }
    }

    private void sendAsync(ConcurrentLinkedQueue<Activity> activitiesToSend) throws PXException {
        List<Activity> activities = activitiesAsList(activitiesToSend);

        try {
            client.sendBatchActivities(activities);
        } catch (Exception e) {
            throw new PXException(e);
        }

    }

    private List<Activity> activitiesAsList(ConcurrentLinkedQueue<Activity> activitiesToSend) {
        List<Activity> activities = new ArrayList<>();
        while (!activitiesToSend.isEmpty()) {
            Activity activity = activitiesToSend.poll();
            activities.add(activity);
        }
        return activities;
    }

    private ConcurrentLinkedQueue<Activity> flush() {
        ConcurrentLinkedQueue<Activity> activitiesToSend = bufferedActivities;
        bufferedActivities = new ConcurrentLinkedQueue<>();
        counter.set(0);
        return activitiesToSend;
    }
}

@@ -308,59 +308,8 @@
 nts;
-%0Aimport edu.emory.mathcs.backport.java.util.Arrays;
 %0A%0Aim
@@ -383,93 +383,12 @@
 til.
-Collections;%0Aimport java.util.List;%0Aimport java.util.concurrent.ConcurrentLinkedDeque
+List
 ;%0Aim
@@ -539,43 +539,8 @@
 ck;%0A
-import java.util.stream.Collectors;
 %0A%0A/*
@@ -2700,65 +2700,67 @@
     
-lock.lock();%0A        if (counter.get() %3E= maxBufferLength
+boolean gainedLock = lock.tryLock();%0A        if (gainedLock
 ) %7B%0A
@@ -2823,158 +2823,243 @@
 Send
- = flush();%0A            lock.unlock();%0A            sendAsync(activitiesToSend);%0A        %7D else %7B    //flush already handled%0A            lock.unlock();
+;%0A            try %7B%0A                activitiesToSend = flush();%0A            %7D finally %7B%0A                lock.unlock();%0A            %7D%0A            if (activitiesToSend != null) %7B%0A                sendAsync(activitiesToSend);%0A            %7D
 %0A   

improve lock