package ru.yandex.wmconsole.periodic;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Required;
import ru.yandex.common.scheduler.ExecutionContext;
import ru.yandex.common.util.collections.Pair;
import ru.yandex.common.util.concurrent.CommonThreadFactory;
import ru.yandex.wmconsole.data.partition.WMCPartition;
import ru.yandex.wmconsole.service.AllAboutUrlService;
import ru.yandex.wmtools.common.error.InternalException;
import ru.yandex.wmtools.common.util.scheduler.timetable.AbstractTaskExecutor;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

/**
 * User: azakharov
 * Date: 19.11.13
 * Time: 19:37
 */
public class AllAboutUrlTask extends AbstractTaskExecutor {

    private static final int THREADS_PER_DB = 4;
    private static final int THREAD_COUNT = 8 * THREADS_PER_DB;

    private static final Logger log = LoggerFactory.getLogger(AllAboutUrlTask.class);

    private ThreadFactory threadFactory;

    private AllAboutUrlService allAboutUrlService;

    public void init() {
        threadFactory = new CommonThreadFactory(true, AllAboutUrlTask.class.getSimpleName() + "-");
    }

    @Override
    public String runWithRELogging(ExecutionContext context) throws InternalException {

        ExecutorService executorService = Executors.newFixedThreadPool(THREAD_COUNT, threadFactory);

        final List<Future> futures = new ArrayList<>();
        for (int i = 0; i < WMCPartition.getHostDbCount(getDatabaseCount()); i++) {
            for (int j = 0; j < THREADS_PER_DB; j++) {
                int startIndex = 512 / THREADS_PER_DB * j ;
                int endIndex = 512 / THREADS_PER_DB * (j+1) - 1;
                log.debug("All about url task submit {} ({},{})", i, startIndex, endIndex);
                Future f = executorService.submit(new UpdateNonKiwiDataTask(i, startIndex, endIndex, allAboutUrlService));
                futures.add(f);
            }
        }

        try {
            long t0 = System.currentTimeMillis();
            while (true) {
                Thread.sleep(60 * 1000);
                long t1 = System.currentTimeMillis();
                if (isDone(futures)) {
                    log.info("All about url tasks done");
                    break;
                }
                if (executorService.isTerminated() || t1 - t0 > 10 * 60 * 1000) {
                    log.warn("All about url task: premature thread stop (terminated={})", executorService.isTerminated());
                    break;
                }
            }

            executorService.shutdown();
            executorService.awaitTermination(5, TimeUnit.MINUTES);
            if (!executorService.isTerminated()) {
                executorService.shutdownNow();
            }
        } catch (InterruptedException e) {
            String msg = "All about url task interrupted";
            log.error(msg, e);
            return msg;
        }
        return "All about url task executed successfully";
    }

    private static boolean isDone(List<Future> futures) {
        for (Future f : futures) {
            if (!f.isDone()) {
                return false;
            }
        }
        return true;
    }

    /**
     * Job responsible for updating given range of partitions for given db shard
     */
    public static class UpdateNonKiwiDataTask implements Runnable {
        private static final Logger log = LoggerFactory.getLogger(UpdateNonKiwiDataTask.class);
        private final int dbNum;
        private final Pair<Integer, Integer> partitionRange;
        private final AllAboutUrlService allAboutUrlService;

        /**
         * Constructor
         *
         * @param dbNum         host db shard number
         * @param fromPartition starts range of partitions to be processed
         * @param toPartition   ends range of partitions to be processed (toPartition included in range and processed)
         */
        public UpdateNonKiwiDataTask(final int dbNum, final int fromPartition, final int toPartition, final AllAboutUrlService allAboutUrlService) {
            this.dbNum = dbNum;
            this.partitionRange = new Pair<>(fromPartition, toPartition);
            this.allAboutUrlService = allAboutUrlService;
        }

        @Override
        public void run() {
            log.debug("All about url task: UpdateNonKiwiDataTask processing start {} ({}, {})", dbNum, partitionRange.getFirst(), partitionRange.getSecond());
            for (int partition = partitionRange.getFirst(); partition <= partitionRange.getSecond(); partition++) {
                if (Thread.interrupted()) {
                    log.warn("All about url task: UpdateNonKiwiDataTask db={} partition={} interrupted, stop", dbNum,  partition);
                    return;
                }
                try {
                    allAboutUrlService.updateNonKiwiData(dbNum, partition);
                } catch (InternalException e) {
                    log.error("All about url task: Internal exception in UpdateNonKiwiDataTask.run", e);
                }
            }
            log.debug("All about url task: UpdateNonKiwiDataTask processing done {} ({}, {})", dbNum, partitionRange.getFirst(), partitionRange.getSecond());
        }
    }

    @Required
    public void setAllAboutUrlService(AllAboutUrlService allAboutUrlService) {
        this.allAboutUrlService = allAboutUrlService;
    }
}
