package de.visone.crawl.xml;

import java.io.IOException;

import org.apache.log4j.Logger;
import org.xml.sax.SAXException;

import de.visone.crawl.Settings;
import de.visone.crawl.gui.AbstractCrawlCard;
import de.visone.crawl.out.CrawlListener;
import de.visone.crawl.sys.CrawlState;
import de.visone.crawl.sys.Helper4Crawler;
import de.visone.crawl.sys.UrlPool;

public class CrawlerThread extends Thread implements ProgressProducer {

    private final String userAgent;

    private final UrlPool pool;

    private final int maxDepth;

    private final CrawlListener listener;

    private final boolean haltOnError;

    private final int coolDown;

    private ProgressListener progress;

    private volatile int level;

    private volatile boolean hasFinished;

    private Exception errorFlag;

    private String lastURL;

    private volatile boolean fresh = true;

    private static final Logger logger = Logger.getLogger(CrawlerThread.class);

    public CrawlerThread(final UrlPool pool, final Settings s,
            final CrawlListener listener) {
        super("Crawl-Thread");
        this.pool = pool;
        maxDepth = s.maxDepth;
        userAgent = s.userAgent;
        this.listener = listener;
        progress = null;
        hasFinished = false;
        haltOnError = s.haltOnError;
        coolDown = s.coolDown;
        errorFlag = null;
        lastURL = null;
    }

    private void finish() {
        final ProgressListener p = progress;
        progress = null;
        p.finished(lastURL, haltOnError ? errorFlag : null);
    }

    @Override
    public void setProgressListener(final ProgressListener p) {
        progress = p;
        if (progress != null) {
            if (hasFinished) {
                finish();
            } else {
                progress.progressAdvanced(getProgressLevel(), maxDepth,
                        getProgress());
            }
        }
    }

    private void crawl(final CrawlState state) throws IOException, SAXException {
        level = state.getDepth();
        lastURL = state.getURL().toString();
        if (progress != null) {
            progress.progressAdvanced(getProgressLevel(), maxDepth,
                    getProgress());
        }
        Helper4Crawler.crawl(state, pool, userAgent);
    }

    @Override
    public void run() {
        fresh = false;
        try {
            while (!isInterrupted() && pool.hasNext()) {
                final CrawlState state = pool.getNext();
                if (state != null) {
                    try {
                        crawl(state);
                        listener.pageCrawled(state.getTexter());
                        state.dispose();
                    } catch (final Exception e) {
                        errorFlag = e;
                        if (!haltOnError) {
                            logger.debug("exception while crawling", e);
                            if (coolDown > 0) {
                                synchronized (this) {
                                    logger.debug("cooling down " + coolDown
                                            + "ms");
                                    wait(coolDown);
                                }
                            }
                        }
                    }
                }
                if (haltOnError && errorFlag != null) {
                    break;
                }
            }
        } catch (final InterruptedException e) {
            interrupt();
        }
        try {
            listener.close();
        } catch (final IOException e) {
            logger.debug("cannot close crawl listener", e);
        }
        hasFinished = true;
        if (progress != null) {
            finish();
        } else if (haltOnError && errorFlag != null && !isInterrupted()) {
            AbstractCrawlCard.printErrorDialog(lastURL, errorFlag, null);
        }
    }

    public void cancelAction() {
        if (progress != null) {
            progress = null;
            interrupt();
        }
    }

    public int getProgressLevel() {
        return level;
    }

    public double getProgress() {
        return pool.getProgress(level);
    }

    @Override
    public boolean isCrawling() {
        return !hasFinished;
    }

    @Override
    public void waitFor() throws InterruptedException {
        join();
    }

    @Override
    public boolean isFresh() {
        return fresh;
    }

}
