package usda.weru.weps.reports;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import javax.swing.event.EventListenerList;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JRParameter;
import net.sf.jasperreports.engine.JRTemplate;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.JasperReport;
import org.apache.log4j.Logger;
import org.jfree.data.category.DefaultCategoryDataset;
import org.openide.util.Exceptions;
import usda.weru.util.About;
import usda.weru.util.ConfigData;
import usda.weru.weps.reports.query.WepsConnection;

/**
 *
 * @author Joseph Levin <joelevin@weru.ksu.edu>
 */
public class ReportPack implements Runnable {

    private static final ExecutorService c_threadService;

    static {
        c_threadService = Executors.newCachedThreadPool();
    }

    private final static Logger LOGGER = Logger.getLogger(ReportPack.class);

    private EventListenerList c_listeners;
    private ReportLoader c_loader;
    private WepsConnection c_connection;
    private String c_reportName;
    private JasperPrint c_print;
    //private Thread c_fillingThread;
    private boolean c_isFilling;
    private JasperReport report;

    private final Freshness c_freshness;

    private Map<String, Object> c_properties;

    private static Semaphore rp_mutex = new Semaphore(1);

    public void getrpMutex() throws InterruptedException {
        rp_mutex.acquireUninterruptibly();
    }

    public void setrpMutex() {
        rp_mutex.release();
    }

    /**
     *
     * @param loader
     * @param connection
     * @param reportName
     */
    public ReportPack(ReportLoader loader, WepsConnection connection, String reportName) {
        this(loader, connection, reportName, null);
    }

    /**
     *
     * @param loader
     * @param connection
     * @param reportName
     * @param freshness
     */
    public ReportPack(ReportLoader loader, WepsConnection connection, String reportName, Freshness freshness) {
        init();
        c_loader = loader;
        c_connection = connection;
        c_reportName = reportName;
        c_connection.setUnits(ConfigData.getDefault().getData(ConfigData.Units));
        c_freshness = freshness;
    }

    private void init() {
        c_listeners = new EventListenerList();
    }

    /**
     *
     * @return
     */
    public WepsConnection getConnection() {
        return c_connection;
    }

    /**
     *
     * @return
     */
    public JasperReport getReport() {
        return report != null ? report : c_loader.getReport(c_reportName);
    }

    /**
     *
     * @param report
     */
    public void setReport(JasperReport report) {
        this.report = report;
    }

    /**
     *
     * @return
     */
    public JasperPrint getPrint() {
        return c_print;
    }

    /**
     *
     * @return
     */
    public String getReportName() {
        return c_reportName;
    }

    /**
     *
     * @return
     */
    public boolean isUpToDate() {
        return c_freshness != null ? c_freshness.isUpToDate() : false;
    }

    private void fireFinished(JasperPrint printed) {
        ReportPackListener[] listeners = c_listeners.getListeners(ReportPackListener.class);
        for (ReportPackListener listener : listeners) {
            //TODO: create a new thread for each one maybe?
            listener.fillFinished(printed);
        }
    }

    private void fireError(Throwable t) {
        ReportPackListener[] listeners = c_listeners.getListeners(ReportPackListener.class);
        for (ReportPackListener listener : listeners) {
            //TODO: create a new thread for each one maybe?
            listener.fillError(t);
        }
    }

    private void fireStarted() {
        ReportPackListener[] listeners = c_listeners.getListeners(ReportPackListener.class);
        for (ReportPackListener listener : listeners) {
            //TODO: create a new thread for each one maybe?            
            listener.fillStarted();
        }
    }

    /**
     *
     */
    public void startFill() {
        synchronized (this) {
            if (!c_isFilling) {
                c_isFilling = true;
                fireStarted();
                c_threadService.submit(this);
            }
        }
    }

    /**
     *
     * @return
     */
    public boolean isFilling() {
        synchronized (this) {
            return c_isFilling;
        }
    }

    /**
     *
     */
    public void waitWhileFilling() {
        //testing busy wait 
        while (isFilling()) {
            //uncomment to block thread and allow other threads to execute
            //may cause/create errors exe other threads 
            Thread.yield();
        }
    }

    //events code
    /**
     *
     * @param listener
     */
    public void addReportPackListener(ReportPackListener listener) {
        c_listeners.add(ReportPackListener.class, listener);
    }

    /**
     *
     * @param listener
     */
    public void removeReportPackListener(ReportPackListener listener) {
        c_listeners.remove(ReportPackListener.class, listener);
    }

    /**
     *
     * @return
     */
    public synchronized Map<String, Object> getReportParameters() {
        if (c_properties == null) {
            ConfigData data = ConfigData.getDefault();
            c_properties = new HashMap<String, Object>();
            c_properties.put("REPORT_LOADER", c_loader);
            c_properties.put("IMAGE_FILENAME", data.getData(ConfigData.ReportFileName));
            c_properties.put("DATE_FORMAT", data.getData(ConfigData.FormatOperationDate));

            //We want to pass the release string to all jasper reports
            String release = "WEPS " + About.getVersion();
            c_properties.put("RELEASE", release);

            //add the templates to all reports
            JRTemplate style = ReportLoader.getDefault().getTemplate("style");
            Collection<JRTemplate> templates = new ArrayList<JRTemplate>();
            templates.add(style);
            c_properties.put(JRParameter.REPORT_TEMPLATES, templates);
        }

        return c_properties;
    }

    /**
     *
     */
    @Override
    public void run() {

        try {
            LOGGER.debug("Filling Report: " + c_reportName + "; " + c_connection.toString());
            c_print = null;
            WepsConnection connection = c_connection;
            JasperReport report2 = getReport();
            if (report2 == null) {
                throw new NullPointerException("No report loaded for: " + c_reportName);
            }
            //System.out.println("Passing to Jasperreports.");
            try {
                this.getrpMutex();
            } catch (InterruptedException ex) {
                Exceptions.printStackTrace(ex);
            }
            JasperPrint printed = JasperFillManager.fillReport(report2, getReportParameters(), connection);
            this.setrpMutex();
            //System.out.println("Recieving from Jasperreports.");
            c_print = printed;
            fireFinished(printed);
            synchronized (this) {
                c_isFilling = false;
            }
        } catch (NullPointerException | JRException t) {
            synchronized (this) {
                c_isFilling = false;
            }
            fireError(t);
        }
    }

    /**
     *
     * @throws Throwable
     */
    @Override
    protected void finalize() throws Throwable {
        //if connection caching is added then this block will need to notify the connection it is no longer required by this pack.
        try {
            if (c_connection != null) {
                c_connection.close();
            }
        } catch (SQLException e) {
            LOGGER.warn("Unable to close WepsConnection", e);
        }
        super.finalize();
    }

    /**
     *
     */
    public interface Freshness {

        /**
         *
         * @return
         */
        public boolean isUpToDate();
    }

    public void connectQuickPlot(ArrayList<DefaultCategoryDataset> col0, ArrayList<DefaultCategoryDataset> col1) throws SQLException {
        c_connection.connectQuickplot(col0, col1);
    }
}
