package org.well.well.core.http;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class HttpConnectionPool implements Runnable {
    private static Logger logger = LoggerFactory
            .getLogger(HttpConnectionPool.class);
    public static final int TEN_SECOND = 10000;
    private List<HttpConnectionInfo> activeHttpConnectionInfos = new ArrayList<HttpConnectionInfo>();
    private List<HttpConnectionInfo> suspendedHttpConnectionInfos = new ArrayList<HttpConnectionInfo>();
    private String urls;
    private AtomicInteger index = new AtomicInteger(0);
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private Thread thread;
    private boolean running;

    public void init() {
        if (urls == null) {
            throw new IllegalArgumentException("urls cannot be null");
        }

        if (urls.trim().length() == 0) {
            throw new IllegalArgumentException("urls cannot be empty");
        }

        for (String url : urls.split(",")) {
            HttpConnectionInfo httpConnectionInfo = new HttpConnectionInfo(url);
            activeHttpConnectionInfos.add(httpConnectionInfo);
        }

        running = true;
        thread = new Thread(this);
        thread.start();
    }

    public void destroy() {
        running = false;
        thread = null;
    }

    public String process(String method, Map<String, String> queryParams,
            Map<String, String> formParams, Map<String, String> headParams) {
        boolean success = false;
        HttpConnectionResult httpConnectionResult = null;

        while (!success) {
            HttpConnectionInfo httpConnectionInfo = this
                    .tryToGetHttpConnectionInfo();
            httpConnectionResult = httpConnectionInfo.process(method,
                    queryParams, formParams, headParams);
            success = httpConnectionResult.isSuccess();

            if (!success) {
                this.suspend(httpConnectionInfo);
            }
        }

        return httpConnectionResult.getContent();
    }

    private HttpConnectionInfo tryToGetHttpConnectionInfo() {
        readWriteLock.readLock().lock();

        try {
            if (activeHttpConnectionInfos.isEmpty()) {
                logger.error("suspended HttpConnectionInfos size is {}",
                        suspendedHttpConnectionInfos.size());
                throw new IllegalStateException(
                        "active HttpConnectionInfos is empty");
            }

            int temperaryIndex = index.incrementAndGet()
                    % activeHttpConnectionInfos.size();

            return activeHttpConnectionInfos.get(temperaryIndex);
        } finally {
            readWriteLock.readLock().unlock();
        }
    }

    private void suspend(HttpConnectionInfo httpConnectionInfo) {
        readWriteLock.writeLock().lock();

        try {
            logger.info("suspend {}", httpConnectionInfo.getUrl());
            activeHttpConnectionInfos.remove(httpConnectionInfo);
            suspendedHttpConnectionInfos.add(httpConnectionInfo);
        } finally {
            readWriteLock.writeLock().unlock();
        }
    }

    private void active(HttpConnectionInfo httpConnectionInfo) {
        readWriteLock.writeLock().lock();

        try {
            logger.info("active {}", httpConnectionInfo.getUrl());
            activeHttpConnectionInfos.add(httpConnectionInfo);
            suspendedHttpConnectionInfos.remove(httpConnectionInfo);
        } finally {
            readWriteLock.writeLock().unlock();
        }
    }

    public void run() {
        while (running) {
            try {
                Thread.sleep(TEN_SECOND);
            } catch (InterruptedException ex) {
                logger.info(ex.getMessage(), ex);
            }

            List<HttpConnectionInfo> checkedHttpConnectionInfos = new ArrayList(
                    suspendedHttpConnectionInfos);
            logger.debug("suspended : {}", checkedHttpConnectionInfos.size());

            for (HttpConnectionInfo httpConnectionInfo : checkedHttpConnectionInfos) {
                logger.debug("check : {}", httpConnectionInfo.getUrl());

                if (httpConnectionInfo.check()) {
                    active(httpConnectionInfo);
                } else {
                    logger.info("{} still broken", httpConnectionInfo.getUrl());
                }
            }
        }
    }

    public void setUrls(String urls) {
        this.urls = urls;
    }

    public List<HttpConnectionInfo> getActiveHttpConnectionInfos() {
        return activeHttpConnectionInfos;
    }

    public List<HttpConnectionInfo> getSuspendedHttpConnectionInfos() {
        return suspendedHttpConnectionInfos;
    }
}
