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.JRRuntimeException;
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 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();
    }


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

    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;
    }
    
//    @Override
//    public void finalize () throws Throwable {
//        super.finalize();
//    }
    
    public void close () {
        try {
            // help w/ garbage collection
            if (c_loader != null) {
                c_loader.close();
                c_loader = null;                
            }
            c_freshness = null;
            c_properties.clear();
            c_listeners = null;
            c_print = null;
            c_connection.close();
            c_connection = null;
        } catch (Exception ex) {
        }
    }
    
//    @Override
//    public void finalize () throws Throwable {
//        super.finalize();
//    }
    
    private void init() {
        c_listeners = new EventListenerList();
    }

    public WepsConnection getConnection() {
        return c_connection;
    }
    
    public void closeConnection () {
        try {
            c_connection.close();
        } catch (Exception ex) {
            int j = 1;
        }
    }

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

    public void setReport(JasperReport report) {
        this.report = report;
    }

    public JasperPrint getPrint() {
        return c_print;
    }

    public String getReportName() {
        return c_reportName;
    }

    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);
        }
        closeConnection();
    }

    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);
            }
        }
    }

    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();
        }
    }

    public void addReportPackListener(ReportPackListener listener) {
        c_listeners.add(ReportPackListener.class, listener);
    }

    public void removeReportPackListener(ReportPackListener listener) {
        c_listeners.remove(ReportPackListener.class, listener);
    }
    
    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));
            //System.out.println(FilterSet.c_qplotname);

            /*
            if (FilterSet.c_qplotname != null) {
                c_properties.put("PLOT_TITLE", FilterSet.c_qplotname);
            }
            */       
            //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<>();
            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 | JRRuntimeException t) {
            synchronized (this) {
                this.setrpMutex();
                c_isFilling = false;
            }
            fireError(t);
            t.printStackTrace();
        }
    }

    //This shouldn't be needed, I think.  Report packs are never lost, as they are all
    //placed into a hashmap.
//    @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 {

    public boolean isUpToDate();
    }

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