/*
 * Copyright 2009 Lari Natri <lari.natri@gmail.com>
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 * 
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 * 
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */


package compid.application;

import net.aavalla.utils.jobpool.Job;
import net.aavalla.utils.jobpool.JobCategory;
import compid.data.DAOFactory;
import compid.data.tuples.DatasetInfo;
import compid.report.ReportPageModel;
import net.aavalla.utils.jobpool.JobResultEvent;
import net.aavalla.utils.jobpool.JobFinishedListener;
import net.aavalla.utils.jobpool.JobPriority;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Vector;
import javax.swing.ComboBoxModel;
import javax.swing.DefaultComboBoxModel;
import javax.swing.DefaultListModel;
import javax.swing.ListModel;
import javax.swing.SwingUtilities;

/**
 *
 * @author Lari Natri
 */
public class DatasetsHandler {

    public class DatasetsRetriever extends Job<Vector<DatasetInfo>,Object> {

        public DatasetsRetriever() {
            super();
            this.setName("Database refresher");
            this.setSilent(true);
            this.addCategory(JobCategory.DB_READ);
            this.addConflictingCategory(JobCategory.DB_WRITE);
            this.setPriority(JobPriority.IMMEDIATE);
        }

        @Override
        protected Vector<DatasetInfo> work(Object p) throws Throwable {
            return DAOFactory.getInstance().getQueryHandler().getDataSetInfos();
        }

    }
    
    private final Vector<DatasetInfo> datasets;
    private final HashMap<Integer,DatasetInfo> map;
    private final HashSet<String> titles;
    private final Object listenersLock = new Object();
    private final HashSet<DatasetsChangedListener> nonSwingListeners;
    private final HashSet<DatasetsChangedListener> swingListeners;

    public DatasetsHandler() {
        this.datasets = new Vector<DatasetInfo>();
        this.map = new HashMap<Integer,DatasetInfo>();
        this.titles = new HashSet<String>();
        this.nonSwingListeners = new HashSet<DatasetsChangedListener>();
        this.swingListeners = new HashSet<DatasetsChangedListener>();
    }

    /**
     * Reloads data from database in a background thread. Listeners will be
     * notified.
     */
    public void refresh() {
        System.out.println("DEBUG: refresh");
        final DatasetsRetriever job = new DatasetsRetriever();

        job.addJobFinishedListener(new JobFinishedListener() {
            @Override @SuppressWarnings("unchecked")
            public void jobFinished(JobResultEvent event) {
                synchronized(DatasetsHandler.this.datasets) {
                    if (event.failed()) {

                    } else {
                        datasets.clear();
                        datasets.addAll((Vector<DatasetInfo>)event.getResult());
                        titles.clear();
                        for (DatasetInfo dsi : datasets) {
                            titles.add(dsi.getName());
                        }
                    }
                }
                DatasetsHandler.this.fireDatasetsChangedEvent(); // notifies listeners
            }
        });

        CompidApp.getApplication().getJobPool().addJob(job);
  
    }

    public boolean isConnected() {
        return DAOFactory.isConnectedToDB();
    }
    
    public String getUrl() {
        return DAOFactory.getInstance().getDatabaseUrl();
    }

    public ComboBoxModel getAsComboBoxModel() {
        synchronized(this.datasets) {
            return new DefaultComboBoxModel(this.datasets);
        }
    }

    public ReportPageModel getAsReportModel() {
        ReportPageModel rep = new ReportPageModel("Datasets in database","");
        rep.addColumn("Title");
        rep.addColumn("Description");
        rep.addColumn("Available Data");
        rep.addColumn("Protein DB");
        rep.addColumn("Source Software");
        rep.addColumn("Parameters");
        rep.addColumn("Added");
        synchronized(this.datasets) {
            for (DatasetInfo dsi : this.datasets) {
                Vector<Object> row = new Vector<Object>();
                row.add(dsi.toString());
                row.add(dsi.getDescription());
                row.add(dsi.getDataCountsStr());
                row.add(dsi.getUsedProtDataBase());
                row.add(dsi.getAlgorithm().toString());
                row.add(dsi.getParameters());
                row.add(dsi.getDate());
                rep.addRow(row);
            }
        }
        rep.setPageIsFinal();
        return rep;
    }

    public ListModel getAsListModel() {
        DefaultListModel lm = new DefaultListModel();
        synchronized(this.datasets) {
            for (DatasetInfo dsi : this.datasets) {
                lm.addElement(dsi);
            }
        }
        return lm;
    }

    public boolean titleExists(String title) {
        synchronized(this.datasets) {
            return titles.contains(title);
        }
    }

    public DatasetInfo getDatasetById(int id) {
        synchronized(this.datasets) {
            return this.map.get(id);
        }
    }

    /**
     * Adds a listener. Each listener will only be added once.
     * @param listener listener to add
     * @return true if listener didn't exist before
     */
    public boolean addDatasetsChangedListener(DatasetsChangedListener listener) {
        synchronized(this.listenersLock) {
            boolean exists  = this.swingListeners.remove(listener);
            boolean exists2 = !(this.nonSwingListeners.add(listener));
            if (exists || exists2) return false;
            return true;
        }
    }

    /**
     * Adds a Swing listener. datasetsChanged event will be run in Event
     * Dispatch Thread.
     * @param listener listener to add
     * @return true if listener didn't exist before
     */
    public boolean addDatasetsChangedListenerSwing(DatasetsChangedListener listener) {
        synchronized(this.listenersLock) {
            boolean exists  = this.nonSwingListeners.remove(listener);
            boolean exists2 = !(this.swingListeners.add(listener));
            if (exists || exists2) return false;
            return true;
        }
    }


    /**
     * Removes the specified listener
     * @param listener listener to remove
     * @return true if listener was registered, false otherwise
     */
    public boolean removeDatasetsChangedListener(DatasetsChangedListener listener) {
        synchronized(this.listenersLock) {
            boolean removed  = this.nonSwingListeners.remove(listener);
            boolean removed2 = this.swingListeners.remove(listener);
            return (removed || removed2);
        }
    }

    private void fireDatasetsChangedEvent() {
        synchronized(this.listenersLock) {
            for (DatasetsChangedListener l : nonSwingListeners) {
                l.datasetsChanged(this);
            }
            if (!swingListeners.isEmpty()) {
               SwingUtilities.invokeLater(new Runnable() {
                    @Override public void run() {
                        synchronized(DatasetsHandler.this.listenersLock) {
                            for (DatasetsChangedListener l : swingListeners) {
                                l.datasetsChanged(DatasetsHandler.this);
                            }
                        }
                    } // run
                });
            }
        } // synchronized
    }


}
