package de.idealo.mongodb.perf.operations;

import com.mongodb.BasicDBObject;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.model.IndexOptions;
import de.idealo.mongodb.perf.MongoDbAccessor;
import org.bson.Document;

import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Created by kay.agahd on 23.11.16.
 */
public abstract class AbstractOperation implements IOperation{


    private final Random random = ThreadLocalRandom.current();
    private final AtomicLong affectedDocs = new AtomicLong();
    final MongoCollection<Document> mongoCollection;
    final MongoDbAccessor mongoDbAccessor;
    final long minId;
    final long maxId;
    final String queriedField;

    public AbstractOperation(MongoDbAccessor mongoDbAccessor, String db, String collection, String queriedField){
        this.mongoDbAccessor = mongoDbAccessor;
        mongoCollection = mongoDbAccessor.getMongoDatabase(db).getCollection(collection);
        this.queriedField = queriedField;

        final IndexOptions options = new IndexOptions();
        options.background(false);
        mongoCollection.createIndex(new BasicDBObject(queriedField, 1), options);
        minId = getMinMax(mongoDbAccessor, queriedField, true);
        maxId = getMinMax(mongoDbAccessor, queriedField, false);

    }

    /**
     *
     * @param threadId
     * @param threadRunCount
     * @param globalRunCount
     * @param randomId
     * @return number of affected documents
     */

    abstract long executeQuery(int threadId, long threadRunCount, long globalRunCount, long selectorId, long randomId);


    private long getMinMax(MongoDbAccessor mongoDbAccessor, String field, boolean min){
        final Document document = mongoDbAccessor.getMinMax(mongoCollection, field, min);
        final Long id = mongoDbAccessor.getLong(document, field);
        if(id != null){
            return id.longValue();
        }
        return 0;
    }

    @Override
    public void operation(int threadId, long threadRunCount, long globalRunCount) {

        final ThreadLocalRandom rnd = ThreadLocalRandom.current();
        final long selectorId = rnd.nextLong(minId, maxId+1l);//2nd paramter is exlusive, thus add 1
        final long randomId = rnd.nextLong();
        LOG.debug("{}: {} {}: {} {}: {} selectorId:{} {}: {}",
                THREAD_ID, threadId,
                THREAD_RUN_COUNT, threadRunCount,
                GLOBAL_RUN_COUNT, globalRunCount,
                selectorId,
                RANDOM_LONG, randomId);
        try {
            final long lAffectedDocs = executeQuery(threadId, threadRunCount, globalRunCount, selectorId, randomId);
            affectedDocs.addAndGet(lAffectedDocs);
        } catch (Exception e) {
            LOG.error("error while executing query on field '{}' with value '{}'", queriedField, selectorId, e);
        }
    }

    @Override
    public long getAffectedDocuments() {
        return affectedDocs.get();
    }


}