/* Copyright (c) 2012,
 * Mathias Kuhring, KuhringM@rki.de, Robert Koch-Institut, Germany, 
 * All rights reserved. For details, please note the license.txt.
 */

package de.rki.ng4.ipig.gui;

import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.net.Authenticator;
import java.net.MalformedURLException;
import java.net.PasswordAuthentication;
import java.text.DecimalFormat;
import java.util.Vector;

import javax.swing.AbstractButton;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.UIManager;
import javax.swing.border.TitledBorder;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

import org.apache.commons.compress.archivers.ArchiveException;
import org.apache.commons.compress.archivers.ArchiveStreamFactory;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
import org.apache.commons.compress.utils.IOUtils;
import org.apache.commons.net.ftp.FTPClient;

import de.rki.ng4.ipig.exceptions.ExitException;
import de.rki.ng4.ipig.tools.Configurator;

/**
 * <p>The Downloader is a gui which helps downloading necessary data for iPiG.</p>
 * 
 * <p>It provides a list of available organism and data which can be selected (loaded from file).
 * Data is downloaded with use of an ftp library (hence, links must be from an ftp server).
 * Optinally, the downloaded archives can be extracted directly.<p>
 * 
 * @author Mathias Kuhring
 *
 */
public class Downloader {

	private JFrame frame;
	private JProgressBar progressBar;

	private JButton btnStart;
	private JButton btnStop;
	private JButton btnSettings;

	private JCheckBox chckbxUseProxy;

	private OutputJTextArea outputWindow;

	private Loader loader;

	// Temporary configuration file to save settings
	private final String conf = "dl.conf";

	private Vector<Organism> orgs;
	private JScrollPane scrollPane_2;
	private JList orglist;
	private JTextField tf_outputPath;
	private JButton btnOutputPath;
	private JLabel lbl_outputPath;
	private JCheckBox chckbxExtractFiles;
	private JCheckBox chckbxAminoAcidSequences;
	private JCheckBox chckbxIdMappings;
	private JCheckBox chckbxGenome;
	private JCheckBox chckbxProteome;
	private JCheckBox chckbxGeneAnnotations;
	private JButton btnExit;
	private AbstractButton btnHelp;
	private JCheckBox chckbxFtpStatus;

	/**
	 * Launch the application.
	 */
	public static void main(String[] args) {
		EventQueue.invokeLater(new Runnable() {
			public void run() {
				try {
					Downloader window = new Downloader();
					window.frame.setVisible(true);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
	}

	/**
	 * Create the application.
	 */
	public Downloader() {
		// try native look and feel
		try {
			UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
		} catch(Exception e) {
			System.out.println("Error setting native LAF: " + e);
		}

		orgs = loadOrganisms();
		initialize();
		updateFields();
	}

	/**
	 * Initialize the contents of the frame.
	 */
	private void initialize() {
		frame = new JFrame();
		frame.addWindowListener(new WindowAdapter() {
			@Override
			public void windowClosing(WindowEvent arg0) {
				saveConfig();
			}
		});
		frame.setIconImage(Toolkit.getDefaultToolkit().getImage(Downloader.class.getResource("/images/rki.png")));
		frame.setResizable(false);
		frame.setTitle("Downloader GUI");
		frame.setBounds(100, 100, 500, 600);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.getContentPane().setLayout(null);

		progressBar = new JProgressBar();
		progressBar.setBounds(10, 394, 473, 19);
		frame.getContentPane().add(progressBar);

		outputWindow = new OutputJTextArea();
		outputWindow.setEditable(false);
		outputWindow.setSize(473, 157);
		outputWindow.setLocation(10, 400);

		JScrollPane scrollPane = new JScrollPane(outputWindow);
		scrollPane.setBounds(10, 424, 473, 133);
		frame.getContentPane().add(scrollPane);

		JPanel panelOptions = new JPanel();
		panelOptions.setBorder(new TitledBorder(UIManager.getBorder("TitledBorder.border"), "Options", TitledBorder.CENTER, TitledBorder.TOP, null, new Color(0, 70, 213)));
		panelOptions.setBounds(10, 284, 474, 45);
		frame.getContentPane().add(panelOptions);
		panelOptions.setLayout(null);

		btnSettings = new JButton("Settings");
		btnSettings.setEnabled(false);
		btnSettings.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent arg0) {
				ProxyDialog.run();
			}
		});
		btnSettings.setBounds(389, 14, 75, 23);
		panelOptions.add(btnSettings);

		chckbxUseProxy = new JCheckBox("Use proxy");
		chckbxUseProxy.addItemListener(new ItemListener() {
			public void itemStateChanged(ItemEvent arg0) {
				if (chckbxUseProxy.isSelected()){
					btnSettings.setEnabled(true);
					Configurator.setProperty("proxyUse", "true");
				}
				else{
					btnSettings.setEnabled(false);
					Configurator.setProperty("proxyUse", "false");
				}
			}
		});
		chckbxUseProxy.setSelected(Boolean.parseBoolean(Configurator.getProperty("proxyUse", "false")));
		chckbxUseProxy.setBounds(301, 14, 75, 23);
		panelOptions.add(chckbxUseProxy);

		chckbxExtractFiles = new JCheckBox("Extract files after download");
		chckbxExtractFiles.addItemListener(new ItemListener() {
			public void itemStateChanged(ItemEvent arg0) {
				if (chckbxExtractFiles.isSelected()){
					Configurator.setProperty("extractFiles", "true");
				}
				else{
					Configurator.setProperty("extractFiles", "false");
				}
			}
		});
		chckbxExtractFiles.setSelected(Boolean.parseBoolean(Configurator.getProperty("extractFiles", "true")));
		chckbxExtractFiles.setBounds(6, 14, 159, 23);
		panelOptions.add(chckbxExtractFiles);

		chckbxFtpStatus = new JCheckBox("Display ftp status");
		chckbxFtpStatus.addItemListener(new ItemListener() {
			public void itemStateChanged(ItemEvent arg0) {
				if (chckbxFtpStatus.isSelected()){
					Configurator.setProperty("ftpStatus", "true");
				}
				else{
					Configurator.setProperty("ftpStatus", "false");
				}
			}
		});
		chckbxFtpStatus.setSelected(Boolean.parseBoolean(Configurator.getProperty("ftpStatus", "false")));
		chckbxFtpStatus.setBounds(181, 14, 109, 23);
		panelOptions.add(chckbxFtpStatus);


		JPanel panelSaveFolder = new JPanel();
		panelSaveFolder.setBorder(new TitledBorder(null, "Select Save Folder", TitledBorder.CENTER, TitledBorder.TOP, null, null));
		panelSaveFolder.setBounds(9, 213, 474, 60);
		frame.getContentPane().add(panelSaveFolder);

		JPanel panelOrganisms = new JPanel();
		panelOrganisms.setBorder(new TitledBorder(null, "Select Organism", TitledBorder.CENTER, TitledBorder.TOP, null, null));
		panelOrganisms.setBounds(10, 11, 473, 135);
		panelOrganisms.setLayout(null);
		frame.getContentPane().add(panelOrganisms);

		orglist = new JList(getOrgList());
		orglist.addListSelectionListener(new ListSelectionListener() {
			public void valueChanged(ListSelectionEvent arg0) {
				changeDataCheckboxes(true);
			}
		});
		orglist.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);

		scrollPane_2 = new JScrollPane(orglist);
		scrollPane_2.setBounds(10, 16, 453, 105);
		panelOrganisms.add(scrollPane_2);
		panelSaveFolder.setLayout(null);

		JPanel panel_outputPath = new JPanel();
		panel_outputPath.setOpaque(false);
		panel_outputPath.setBounds(new Rectangle(236, 27, 1, 1));
		panel_outputPath.setLayout(null);
		panel_outputPath.setBounds(10, 14, 455, 36);
		panelSaveFolder.add(panel_outputPath);

		tf_outputPath = new JTextField();
		tf_outputPath.setEditable(false);
		tf_outputPath.setText("");
		tf_outputPath.setColumns(10);
		tf_outputPath.setBounds(0, 14, 356, 20);
		panel_outputPath.add(tf_outputPath);

		btnOutputPath = new JButton("Open");
		btnOutputPath.setBounds(366, 13, 89, 23);
		btnOutputPath.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent arg0) {
				File file = choosePath(new File(tf_outputPath.getText()));
				if (file != null){
					Configurator.setProperty("outputPath",file.getAbsolutePath());
					updateFields();
				}
			}
		});
		panel_outputPath.add(btnOutputPath);

		lbl_outputPath = new JLabel("Output Path:");
		lbl_outputPath.setBounds(0, 0, 374, 14);

		panel_outputPath.add(lbl_outputPath);

		JPanel panelData = new JPanel();
		panelData.setBorder(new TitledBorder(null, "Select Data", TitledBorder.CENTER, TitledBorder.TOP, null, null));
		panelData.setBounds(10, 157, 474, 45);
		frame.getContentPane().add(panelData);
		panelData.setLayout(null);

		chckbxGeneAnnotations = new JCheckBox("Gene Annotations");
		chckbxGeneAnnotations.setEnabled(false);
		chckbxGeneAnnotations.addItemListener(new ItemListener() {
			public void itemStateChanged(ItemEvent arg0) {
				if (chckbxGeneAnnotations.isSelected()){
					Configurator.setProperty("loadGenes", "true");
				}
				else{
					Configurator.setProperty("loadGenes", "false");
				}
			}
		});
		chckbxGeneAnnotations.setSelected(Boolean.parseBoolean(Configurator.getProperty("loadGenes", "true")));
		chckbxGeneAnnotations.setBounds(6, 14, 111, 23);
		panelData.add(chckbxGeneAnnotations);

		chckbxAminoAcidSequences = new JCheckBox("Amino Acid Seqs.");
		chckbxAminoAcidSequences.setEnabled(false);
		chckbxAminoAcidSequences.addItemListener(new ItemListener() {
			public void itemStateChanged(ItemEvent arg0) {
				if (chckbxAminoAcidSequences.isSelected()){
					Configurator.setProperty("loadAaseqs", "true");
				}
				else{
					Configurator.setProperty("loadAaseqs", "false");
				}
			}
		});
		chckbxAminoAcidSequences.setSelected(Boolean.parseBoolean(Configurator.getProperty("loadAaseqs", "true")));
		chckbxAminoAcidSequences.setBounds(119, 14, 111, 23);
		panelData.add(chckbxAminoAcidSequences);

		chckbxIdMappings = new JCheckBox("ID Mappings");
		chckbxIdMappings.setEnabled(false);
		chckbxIdMappings.addItemListener(new ItemListener() {
			public void itemStateChanged(ItemEvent arg0) {
				if (chckbxIdMappings.isSelected()){
					Configurator.setProperty("loadIdmappings", "true");
				}
				else{
					Configurator.setProperty("loadIdmappings", "false");
				}
			}
		});
		chckbxIdMappings.setSelected(Boolean.parseBoolean(Configurator.getProperty("loadIdmappings", "true")));
		chckbxIdMappings.setBounds(232, 14, 85, 23);
		panelData.add(chckbxIdMappings);

		chckbxProteome = new JCheckBox("Proteome");
		chckbxProteome.setEnabled(false);
		chckbxProteome.addItemListener(new ItemListener() {
			public void itemStateChanged(ItemEvent arg0) {
				if (chckbxProteome.isSelected()){
					Configurator.setProperty("loadProts", "true");
				}
				else{
					Configurator.setProperty("loadProts", "false");
				}
			}
		});
		chckbxProteome.setSelected(Boolean.parseBoolean(Configurator.getProperty("loadProts", "true")));
		chckbxProteome.setBounds(319, 14, 71, 23);
		panelData.add(chckbxProteome);

		chckbxGenome = new JCheckBox("Genome");
		chckbxGenome.setEnabled(false);
		chckbxGenome.addItemListener(new ItemListener() {
			public void itemStateChanged(ItemEvent arg0) {
				if (chckbxGenome.isSelected()){
					Configurator.setProperty("loadChroms", "true");
				}
				else{
					Configurator.setProperty("loadChroms", "false");
				}
			}
		});
		chckbxGenome.setSelected(Boolean.parseBoolean(Configurator.getProperty("loadChroms", "true")));
		chckbxGenome.setBounds(392, 14, 65, 23);
		panelData.add(chckbxGenome);

		JPanel panelDownload = new JPanel();
		panelDownload.setBorder(new TitledBorder(null, "Download", TitledBorder.CENTER, TitledBorder.TOP, null, null));
		panelDownload.setLayout(null);
		panelDownload.setBounds(10, 340, 147, 44);
		frame.getContentPane().add(panelDownload);

		JPanel panel_1 = new JPanel();
		panel_1.setLayout(null);
		panel_1.setBounds(6, 14, 135, 23);
		panelDownload.add(panel_1);

		btnStart = new JButton("Start");
		btnStart.setBounds(0, 0, 65, 23);
		panel_1.add(btnStart);
		btnStart.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent arg0) {
				loader = new Loader();
				loader.start();
			}
		});

		btnStop = new JButton("Stop");
		btnStop.setBounds(70, 0, 65, 23);
		panel_1.add(btnStop);
		btnStop.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent arg0) {
				if (loader != null && loader.isAlive()){
					Configurator.setBreak(true);
					btnStop.setEnabled(false);
				}
				else{
					enableButtons(true);
					Configurator.setBreak(false);
					outputWindow.redirectSystemStreams(false, false);
				}	
			}
		});
		btnStop.setEnabled(false);

		JPanel panelGeneral = new JPanel();
		panelGeneral.setBorder(new TitledBorder(UIManager.getBorder("TitledBorder.border"), "General", TitledBorder.CENTER, TitledBorder.TOP, null, new Color(0, 70, 213)));
		panelGeneral.setLayout(null);
		panelGeneral.setBounds(336, 339, 147, 44);
		frame.getContentPane().add(panelGeneral);

		JPanel panel_3 = new JPanel();
		panel_3.setLayout(null);
		panel_3.setOpaque(false);
		panel_3.setBounds(6, 14, 135, 23);
		panelGeneral.add(panel_3);

		btnHelp = new JButton("Help");
		btnHelp.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent arg0) {
				try {
					String help = "/help/downloader_help.txt";
					InputStream is = Downloader.class.getResourceAsStream(help);
					BufferedReader helpbf = new BufferedReader(new InputStreamReader(is));
					outputWindow.setText("");
					while (helpbf.ready()){
						outputWindow.append(helpbf.readLine() + "\n");
					}
					outputWindow.setCaretPosition(0); 
					is.close();
				} catch (IOException e) {
					outputWindow.append("error: " + e.getLocalizedMessage());
				}
			}
		});
		btnHelp.setBounds(0, 0, 65, 23);
		panel_3.add(btnHelp);

		btnExit = new JButton("Exit");
		btnExit.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent arg0) {
				saveConfig();
				System.exit(0);
			}
		});
		btnExit.setBounds(70, 0, 65, 23);
		panel_3.add(btnExit);
	}

	// a thread running the download and controlling the output and buttons meanwhile
	class Loader extends Thread{
		@Override public void run(){
			Configurator.setBreak(false);
			enableButtons(false);
			outputWindow.setText("");
			outputWindow.redirectSystemStreams(true, true);

			try {
				load();
			} catch (ExitException e) {
				System.out.println("\n" + e.getMessage());
			} catch (Exception e) {
				System.out.println(e.getMessage());
			}

			outputWindow.redirectSystemStreams(false, false);
			System.gc();
			enableButtons(true);
			Configurator.setBreak(false);
		}
	}

	// enabling/disabling of buttons for running the downloads
	// stop button is always set contradictory
	private void enableButtons(boolean b){
		btnStart.setEnabled(b);
		btnStop.setEnabled(!b);

		btnOutputPath.setEnabled(b);

		orglist.setEnabled(b);

		btnHelp.setEnabled(b);
		btnExit.setEnabled(b);

		chckbxExtractFiles.setEnabled(b);
		chckbxFtpStatus.setEnabled(b);
		chckbxUseProxy.setEnabled(b);
		btnSettings.setEnabled(b && chckbxUseProxy.isSelected());

		changeDataCheckboxes(b);
	}

	// enabling/disabling of data checkboxes 
	private void changeDataCheckboxes(boolean b){
			boolean orgSelected = !orglist.isSelectionEmpty();
			Organism org = orgs.get(orglist.getSelectedIndex());
			chckbxGeneAnnotations.setEnabled(b && orgSelected && org.isGenesAvailable());
			chckbxAminoAcidSequences.setEnabled(b && orgSelected && org.isAaseqsAvailable());
			chckbxIdMappings.setEnabled(b && orgSelected && org.isIdmappingsAvailable());
			chckbxProteome.setEnabled(b && orgSelected && org.isProtsAvailable());
			chckbxGenome.setEnabled(b && orgSelected && org.isChromsAvailable());
	}

	// function run by the download thread (Loader). Downloads all selected files and extracts them if wanted.
	private void load() throws ExitException, MalformedURLException {
		if (!isSelectionMissing()){
			Organism org = orgs.get(orglist.getSelectedIndex());
			Vector<String> links = org.getLinks();
			Vector<File> localFiles = new Vector<File>();
			String outputPath = new File(Configurator.getProperty("outputPath","")).getAbsolutePath() + "/";
			String filename;
			File file;
			boolean write;
			boolean loaded;

			for (String link : links){
				Configurator.checkBreak();
				filename = link.substring(link.lastIndexOf("/"), link.length());
				file = new File(outputPath + filename);
				write = true;
				loaded = false;
				System.out.println("initializing download of " + file.getName());
				if (file.exists()) write = questionDialog(file.getName());
				if (write) {
					loaded = downloadFile(link, file.getAbsolutePath());
				}
				if (loaded || !write) localFiles.add(file);
			}

			File outfile;
			if (Boolean.parseBoolean(Configurator.getProperty("extractFiles", "true"))){
				for (File localFile : localFiles){
					Configurator.checkBreak();
					filename = localFile.getAbsolutePath();
					outfile = new File(filename.substring(0, filename.lastIndexOf('.')));
					write = true;
					System.out.println("initializing extraction of " + localFile.getName());
					if (outfile.exists()) write = questionDialog(outfile.getName());
					if (write) {
						unpack(localFile);
					}
				}
			}

			System.out.println("done");
		}
	}

	// function to download a file from a ftp server. ftp server is extracted from the link.
	// user is always "anonymous". Proxy settings are taken from Configurator, if necessary.
	private boolean downloadFile(String link, String filename) throws ExitException {
		try {
			if (Boolean.parseBoolean(Configurator.getProperty("proxyUse", "false"))){
				initProxy(true);
				initAuthenticator();
			}
			else{
				initProxy(false);
				Authenticator.setDefault(null);
			}

			// FTP settings
			boolean status = Boolean.parseBoolean(Configurator.getProperty("ftpStatus", "false"));
			boolean ftpOk = true;

			FTPClient ftp = new FTPClient();
			String hostname = link.substring(0, link.indexOf("/"));
			String remote = link.substring(link.indexOf("/"), link.length());

			ftp.connect(hostname);			
			ftp.enterLocalPassiveMode();
			if( status ) { System.out.print( " ftp status: " + ftp.getReplyString() ); }

			ftpOk &= ftp.login("anonymous","my@mail.address");
			if( status ) { System.out.print( " ftp status: " + ftp.getReplyString() ); }

			ftpOk &= ftp.setFileType(FTPClient.BINARY_FILE_TYPE);
			if( status ) { System.out.print( " ftp status: " + ftp.getReplyString() ); }

			if (!ftpOk){
				System.out.println("connection to ftp server refused");
				return false;
			}

			double max = 0;
			try {
				max = ftp.listFiles(remote)[0].getSize();
			}
			catch (Exception e){
				System.out.println( " file size not received -> progress not available!" );
			}

			FileOutputStream out = new FileOutputStream(filename);
			InputStream is = ftp.retrieveFileStream(remote);
			if( status ) { System.out.print( " ftp status: " + ftp.getReplyString() ); }

			try{
				progressBar.setValue(0);
				progressBar.setMaximum(Integer.MAX_VALUE);
				System.out.println(" waiting for file (" + (new DecimalFormat("###0.00")).format(max/1024/1024) + " MB)");

				byte[] buffer = new byte[1024];
				for( int n; (n = is.read(buffer)) != -1; out.write(buffer, 0, n) ){
					progressBar.setValue((int) (((double) progressBar.getValue()) + (((double) n) / ((double) max) * ((double) Integer.MAX_VALUE))));
					Configurator.checkBreak();
				};
				progressBar.setValue(progressBar.getMaximum());
			}
			finally{
				is.close();
				out.close();
				ftp.disconnect();
			}
		} catch (MalformedURLException e) {
			System.out.println(" can not download file:\n  " + e.getMessage());
			return false;
		} catch (FileNotFoundException e) {
			System.out.println(" can not download file:\n  " + e.getMessage());
			return false;
		} catch (IOException e) {
			System.out.println(" can not download file:\n  " + e.getMessage());
			return false;
		}
		return true;
	}

	// save current configuration to a file. It's used when the window/program is closed.
	private void saveConfig(){
		try {
			Configurator.saveProperties(conf);
		} catch (ExitException e) {
			//			JOptionPane.showMessageDialog(null, "Could not save configuration.\nError message: " + e.getMessage());
		}
	}

	// enables/disables proxy usage with settings from the Configurator.
	private void initProxy(boolean b) throws ExitException{
		try{
			if (b){
				String host = Configurator.getProperty("proxyHost","");
				int port = Integer.parseInt(Configurator.getProperty("proxyPort",""));

				System.setProperty("proxyHost",host);
				System.setProperty("proxyPort",Integer.toString(port));
			}
			else{
				System.setProperty("proxyHost","");
				System.setProperty("proxyPort","");
			}
		}
		catch (NumberFormatException e){
			throw new ExitException("error: port is not a number");
		}
	}

	// enables/disables authentication. May be necessary for proxy usage.
	private void initAuthenticator(){
		if (Boolean.parseBoolean(Configurator.getProperty("proxyAuth","false"))){
			Authenticator.setDefault(new Authenticator() {
				@Override protected PasswordAuthentication getPasswordAuthentication() {
					String user = Configurator.getProperty("proxyUser", "");
					String pass = Configurator.getProperty("proxyPass", "");
					return new PasswordAuthentication(user, pass.toCharArray());
				}
			});
		}
		else{
			Authenticator.setDefault(null);
		}
	}

	// loads the download links for organisms indicated in the file "organisms" (as resource stream).
	private Vector<Organism> loadOrganisms(){
		Vector<Organism> orgs = new Vector<Organism>();
		try {
			String help = "/organisms";
			InputStream is = Downloader.class.getResourceAsStream(help);
			BufferedReader orgbf = new BufferedReader(new InputStreamReader(is));

			String line;
			while ((line = orgbf.readLine()) != null){
				try{
					if (line.startsWith("org")){
						orgs.add(new Organism(line.split("=")[1]));
					}
					else if (line.startsWith("genes")){
						orgs.lastElement().setGenes(line.split("=")[1]);
					}
					else if (line.startsWith("aaseqs")){
						orgs.lastElement().setAaseqs(line.split("=")[1]);
					}
					else if (line.startsWith("idmappings")){
						orgs.lastElement().setIdmappings(line.split("=")[1]);
					}
					else if (line.startsWith("prots")){
						orgs.lastElement().setProts(line.split("=")[1]);
					}
					else if (line.startsWith("chroms")){
						orgs.lastElement().addChroms(line.split("=")[1]);
					}
				}catch (ArrayIndexOutOfBoundsException e){
					// exception prob. means that there is not text/link after an equal sign.
					// this is ignored here and an Organism just don't return empty links than.
				}
			}
			is.close();
		} catch (IOException e) {
			outputWindow.append("error: " + e.getLocalizedMessage());
		}
		return orgs;
	}

	// returns a list with available organisms by name.
	private Vector<String> getOrgList(){
		Vector<String> orgList = new Vector<String>();
		for (Organism o : orgs){
			orgList.add(o.getName());
		}
		return orgList;
	}

	// An Organism keeps the links of the different data sources and returns them at once for the download (depending on user settings).
	// It's used by the "loadOrganisms" and the "load" functions.
	private class Organism{
		private String name;
		private String genes;
		private String aaseqs;
		private String idmappings;
		private String prots;
		private Vector<String> chroms;

		public Organism(String name){
			this.setName(name);
			chroms = new Vector<String>();
		}

		public Vector<String> getLinks(){
			Vector<String> links = new Vector<String>();
			if (isGenesAvailable() && Boolean.parseBoolean(Configurator.getProperty("loadGenes", "true"))) links.add(genes);
			if (isAaseqsAvailable() && Boolean.parseBoolean(Configurator.getProperty("loadAaseqs", "true"))) links.add(aaseqs);
			if (isIdmappingsAvailable() && Boolean.parseBoolean(Configurator.getProperty("loadIdmappings", "true"))) links.add(idmappings);
			if (isProtsAvailable() && Boolean.parseBoolean(Configurator.getProperty("loadProts", "true"))) links.add(prots);
			if (isChromsAvailable() && Boolean.parseBoolean(Configurator.getProperty("loadChroms", "true"))){
				for (String chrom : chroms) links.add(chrom);
			}
			return links;
		}

		public String getName() {
			return name;
		}

		public void setName(String name) {
			this.name = name;
		}

		public void setGenes(String genes) {
			this.genes = genes;
		}

		public void setAaseqs(String aaseqs) {
			this.aaseqs = aaseqs;
		}

		public void setIdmappings(String idmappings) {
			this.idmappings = idmappings;
		}

		public void setProts(String prots) {
			this.prots = prots;
		}

		public void addChroms(String chroms) {
			this.chroms.add(chroms);
		}

		public boolean isGenesAvailable(){
			return genes!=null;
		}

		public boolean isAaseqsAvailable(){
			return aaseqs!=null;
		}

		public boolean isIdmappingsAvailable(){
			return idmappings!=null;
		}

		public boolean isProtsAvailable(){
			return prots!=null;
		}

		public boolean isChromsAvailable(){
			return chroms.size()>0;
		}
	}

	// creation of a FileChooser for the path selection buttons
	private File choosePath(File dir){
		JFileChooser fc = new JFileChooser();
		fc.setCurrentDirectory(dir);
		fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);

		int state = fc.showOpenDialog( null );

		if ( state == JFileChooser.APPROVE_OPTION )
		{
			return fc.getSelectedFile();
		}
		else
			return null;
	}

	// updates the filename and parameter fields, used after selecting a file/folder
	private void updateFields(){
		tf_outputPath.setText(Configurator.getProperty("outputPath", ""));
	}

	// tests if an organism and available data is selected to download
	private boolean isSelectionMissing(){
		StringBuffer message = new StringBuffer();
		boolean selectionMissing = false;
		if (orglist.isSelectionEmpty()){
			selectionMissing = true;
			message.append("no organism selected\n");
		}
		else {
			Organism org = orgs.get(orglist.getSelectedIndex());
			if (!(chckbxGeneAnnotations.isSelected() && org.isGenesAvailable() || 
					chckbxAminoAcidSequences.isSelected() && org.isAaseqsAvailable()|| 
					chckbxIdMappings.isSelected() && org.isIdmappingsAvailable() ||
					chckbxProteome.isSelected() && org.isProtsAvailable() || 
					chckbxGenome.isSelected() && org.isChromsAvailable())){
				selectionMissing = true;
				message.append("no data selected\n");
			}
		}
		if (Configurator.getProperty("outputPath","").matches("")){
			selectionMissing = true;
			message.append("no output path indicated\n");
		}
		if (selectionMissing){
			System.out.println(message);
		}
		return selectionMissing;
	}

	// opens a question dialog, asking to overwrite/skip an existing file
	public boolean questionDialog(String filename) throws ExitException{
		Object[] options = {"Overwrite", "Skip", "Cancel"};
		int n = JOptionPane.showOptionDialog(frame,
				"File already exists:\n" + filename ,
				"File already exists",
				JOptionPane.YES_NO_CANCEL_OPTION,
				JOptionPane.QUESTION_MESSAGE,
				null,
				options,
				options[2]);
		if (n == 0) return true;
		else if (n == 1) return false;
		else throw new ExitException("downloads canceled");
	}

	// extracts an gz file and reports its progress with the progress bar
	// tar archives are extracted in the end
	private void unpack(File file) throws ExitException{
		try {
			System.out.println(" extracting " + file.getName() + " (" + (new DecimalFormat("###0.00")).format(file.length()/1024/1024) + " MB)");
			long size = 0;
			try{
				size = getGzSize(file);
				System.out.println(" expected output size: " + (new DecimalFormat("###0.00")).format(size/1024/1024) + " MB");
			}
			catch (IOException e) {
				System.out.println(" couldn't estimate extracted file size:\n" + 
						" " + e.getMessage() + "");
			}

			GzipCompressorInputStream gzIn = new GzipCompressorInputStream(new BufferedInputStream(new FileInputStream(file)));
			String gzFileName = file.getAbsolutePath();
			String fileName = gzFileName.substring(0, gzFileName.lastIndexOf('.'));
			FileOutputStream out = new FileOutputStream(fileName);

			try{
				progressBar.setValue(0);
				progressBar.setMaximum(Integer.MAX_VALUE);

				final byte[] buffer = new byte[1024];
				int n = 0;

				
				while (-1 != (n = gzIn.read(buffer))) {
					out.write(buffer, 0, n);
					progressBar.setValue((int) (((double) progressBar.getValue()) + (((double) n) / ((double) size) * ((double) Integer.MAX_VALUE))));
					Configurator.checkBreak();
				}
				progressBar.setValue(progressBar.getMaximum());
			}
			finally{
				out.close();
				gzIn.close();
			}
			
			if (fileName.endsWith(".tar")){
				untar(new File(fileName));
			}
		} catch (FileNotFoundException e) {
			System.out.println(" can not extract file:\n  " + e.getMessage());
		} catch (IOException e) {
			System.out.println(" can not extract file:\n  " + e.getMessage());
		}
	}

	// extracts original file size of gz packed data from the last four bytes of the file
	// (see gz file definition for details about that)
	private long getGzSize(File file) throws IOException{
		long val = 0;
		RandomAccessFile raf = new RandomAccessFile(file, "r");
		raf.seek(raf.length() - 4);
		long b4 = raf.read();
		long b3 = raf.read();
		long b2 = raf.read();
		long b1 = raf.read();
		val = (b1 << 24) + (b2 << 16) + (b3 << 8) + b4;			
		raf.close();
		return val;
	}

	// extracts files from a tar archive into a folder named after the archive
	// reports the progress to the progress bar (in form of ith file of n files)
	private void untar(File tarfile) throws ExitException{
		long size = tarfile.length();
		System.out.println(" extracting " + tarfile.getName() + " (" + (new DecimalFormat("###0.00")).format(size/1024/1024) + " MB)");

		String dirName = tarfile.getParent() + "/" + tarfile.getName().substring(0, tarfile.getName().lastIndexOf(".tar"));
		File dir = new File(dirName);

		boolean write = true;
		if (dir.exists()) write = questionDialog(dir.getName());
		else dir.mkdir();

		if (write){
			try {
				InputStream is = new FileInputStream(tarfile);
				TarArchiveInputStream in = (TarArchiveInputStream) 
						new ArchiveStreamFactory().createArchiveInputStream("tar", is); 

				try {
					progressBar.setValue(0);
					progressBar.setMaximum(Integer.MAX_VALUE);
					TarArchiveEntry entry;
					while ((entry = in.getNextTarEntry()) != null){
						OutputStream out = new FileOutputStream(new File(dir.getAbsolutePath(), entry.getName())); 
						try {
							IOUtils.copy(in, out);
						}
						finally {
							out.close();
						}
						progressBar.setValue((int) (((double) progressBar.getValue()) + (((double) entry.getSize()) / ((double) size) * ((double) Integer.MAX_VALUE))));
						Configurator.checkBreak();
					}
					progressBar.setValue(progressBar.getMaximum());
				}
				finally {
					in.close();
				}
			} catch (FileNotFoundException e) {
				System.out.println(" can not extract file:\n  " + e.getMessage());
			} catch (ArchiveException e) {
				System.out.println(" can not extract file:\n  " + e.getMessage());
			} catch (IOException e) {
				System.out.println(" can not extract file:\n  " + e.getMessage());
			}
		}
	}
}
