/*
 * BioJava development code
 *
 * This code may be freely distributed and modified under the
 * terms of the GNU Lesser General Public Licence.  This should
 * be distributed with the code.  If you do not have a copy,
 * see:
 *
 *      http://www.gnu.org/copyleft/lesser.html
 *
 * Copyright for this code is held jointly by the individual
 * authors.  These should be listed in @author doc comments.
 *
 * For more information on the BioJava project and its aims,
 * or to join the biojava-l mailing list, visit the home page
 * at:
 *
 *      http://www.biojava.org/
 *
 * Author: Daniel Asarnow
 * Date:   2012-6-23
 */

package org.biojava.nbio.structure.cath;

import org.biojava.nbio.structure.align.util.UserConfiguration;
import org.biojava.nbio.structure.io.util.FileDownloadUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.biojava.nbio.core.util.InputStreamProvider;

import java.io.*;
import java.net.URL;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * @author Daniel Asarnow
 */
public class CathInstallation implements CathDatabase{

	private static final Logger LOGGER = LoggerFactory.getLogger(CathInstallation.class);
	
	public static final String DEFAULT_VERSION = CathFactory.DEFAULT_VERSION;

	public static final String domainListFileName = "cath-domain-list-v%s.txt"; 
	public static final String domainDescriptionFileName = "cath-domain-description-file-v%s.txt";
	public static final String nodeListFileName = "cath-names-v%s.txt";
	public static final String domallFileName = "cath-domain-boundaries-v%s.txt";
	
	public static final String CATH_DOWNLOAD_URL                     = "http://download.cathdb.info/cath/releases/";
	public static final String CATH_DOWNLOAD_LATEST_RELEASE_DIR      = "latest-release";
	public static final String CATH_DOWNLOAD_PREV_RELEASE_DIR        = "previous-releases";
	public static final String CATH_DOWNLOAD_CLASSIFICATION_DATA_DIR = "cath-classification-data";

	public static final String NEWLINE = System.getProperty("line.separator");;
	public static final String FILESPLIT = System.getProperty("file.separator");;

	
	private String cathVersion;

	private String cathDownloadUrl;
	
	private String cacheLocation ;

	private AtomicBoolean installedDomainList;
	private AtomicBoolean installedDomainDescription;
	private AtomicBoolean installedNodeList;
	private AtomicBoolean installedDomall;

	private final boolean useCathDomainDescriptionFile;
	private final boolean parseCathFragments;

	private Map<String, List<CathDomain>> pdbMap;
	private Map<String, CathDomain> domainMap;
	private Map<String, CathNode> cathTree;
	private Map<String, List<CathFragment>> fragmentMap;



	public CathInstallation(String cacheLocation, boolean usingCDDF, boolean parseCF) {
		setCacheLocation(cacheLocation);

		useCathDomainDescriptionFile = usingCDDF;
		parseCathFragments = parseCF;

		installedDomainDescription = new AtomicBoolean(false);
		installedDomainList = new AtomicBoolean(false);
		installedNodeList = new AtomicBoolean(false);
		installedDomall = new AtomicBoolean(false);

		cathVersion = DEFAULT_VERSION;
		cathDownloadUrl = CATH_DOWNLOAD_URL;

		pdbMap = new HashMap<String, List<CathDomain>>();
		domainMap = new HashMap<String ,CathDomain>();
		cathTree = new HashMap<String, CathNode>();

		if (parseCathFragments) fragmentMap = new HashMap<String,List<CathFragment>>();

	}

	public CathInstallation(String cacheLocation) {
		this(cacheLocation, false, false);
	}

	public CathInstallation() {
		this((new UserConfiguration()).getCacheFilePath());
	}

	public String getDomainListFileName() {
		return cacheLocation + buildFileName(domainListFileName);
	}

	public String getDomainDescriptionFileName() {
		return cacheLocation + buildFileName(domainDescriptionFileName);
	}

	public String getNodeListFileName() {
		return cacheLocation + buildFileName(nodeListFileName);
	}

	public String getDomallFileName() {
		return cacheLocation + buildFileName(domallFileName);
	}
	
	private String buildFileName(String fileNameTemplate) {
		return String.format(fileNameTemplate, cathVersion);
	}
	
	private String buildUrl(String remoteFileName) {
		String remoteFileNameWithVer =  buildFileName(remoteFileName);
		String releasesDir = CATH_DOWNLOAD_LATEST_RELEASE_DIR;
		if (!cathVersion.equals(CathFactory.LATEST_VERSION)) {
			releasesDir = CATH_DOWNLOAD_PREV_RELEASE_DIR;
		}
		return cathDownloadUrl + releasesDir + "/v" + cathVersion + "/" + CATH_DOWNLOAD_CLASSIFICATION_DATA_DIR + "/" + remoteFileNameWithVer;
	}

	public String getCathDownloadUrl() {
		return cathDownloadUrl;
	}

	public void setCathDownloadUrl(String cathDownloadUrl) {
		this.cathDownloadUrl = cathDownloadUrl;
	}

	public String getCacheLocation() {
		return cacheLocation;
	}

	public void setCacheLocation(String cacheLocation) {
		if ( !cacheLocation.endsWith(FILESPLIT) ) cacheLocation += FILESPLIT;
		this.cacheLocation = cacheLocation;
	}

	public AtomicBoolean getInstalledDomainList() {
		return installedDomainList;
	}

	public void setInstalledDomainList(AtomicBoolean installedDomainList) {
		this.installedDomainList = installedDomainList;
	}

	public AtomicBoolean getInstalledDomainDescription() {
		return installedDomainDescription;
	}

	public void setInstalledDomainDescription(AtomicBoolean installedDomainDescription) {
		this.installedDomainDescription = installedDomainDescription;
	}

	public AtomicBoolean getInstalledNodeList() {
		return installedNodeList;
	}

	public AtomicBoolean getInstalledDomall() {
		return installedDomall;
	}

	public void setInstalledNodeList(AtomicBoolean installedNodeList) {
		this.installedNodeList = installedNodeList;
	}

	public void setInstalledDomall(AtomicBoolean installedDomall) {
		this.installedDomall = installedDomall;
	}

	@Override
	public String getCathVersion() {
		return cathVersion;
	}

	@Override
	public CathNode getCathNode(String nodeId) {
		ensureNodeListInstalled();
		return cathTree.get(nodeId);
	}

	@Override
	public List<CathDomain> getByCategory(CathCategory category) {
		if (useCathDomainDescriptionFile) {
			ensureDomainDescriptionInstalled();
		} else {
			ensureDomallInstalled();
		}
		ensureNodeListInstalled();
		List<CathDomain> matches = new ArrayList<CathDomain>();
		CathNode node;
		for ( String nodeId : cathTree.keySet() ) {
			if ( (node = cathTree.get(nodeId)).getCategory() == category ) {
				matches.add( domainMap.get( node.getRepresentative() ) );
			}
		}
		return matches;
	}

	@Override
	public List<CathDomain> filterByCathCode(String query) {
		if (useCathDomainDescriptionFile) {
			ensureDomainDescriptionInstalled();
		} else {
			ensureDomallInstalled();
		}
		List<CathDomain> matches = new ArrayList<CathDomain>();
		for ( String k : domainMap.keySet() ) {
			if ( domainMap.get(k).getCATH().startsWith(query) ) {
				matches.add( domainMap.get(k) );
			}
		}
		return matches;
	}

	@Override
	public List<CathNode> getTree(CathDomain domain) {
		CathNode node = getCathNode( domain.getCATH() );
		List<CathNode> tree = new ArrayList<CathNode>();
		while (node != null) {
			node = getCathNode( node.getParentId() );
			if (node != null) tree.add(node);
		}
		Collections.reverse(tree);
		return tree;
	}

	@Override
	public List<CathDomain> filterByNodeName(String query) {
		ensureNodeListInstalled();
		List<CathNode> matchingNodes = new ArrayList<CathNode>();
		CathNode node;
		for ( String nodeId : cathTree.keySet() ) {
			if ( (node = cathTree.get(nodeId) ).getDescription().startsWith(query) ) {
				matchingNodes.add(node);
			}
		}
		List<CathDomain> matches = new ArrayList<CathDomain>();
		for (CathNode n : matchingNodes) {
			matches.addAll(getDomainsByNodeId(n.getNodeId()));
		}
		return matches;
	}

	@Override
	public List<CathDomain> filterByDescription(String query) {
		if (useCathDomainDescriptionFile) {
			ensureDomainDescriptionInstalled();
		} else {
			ensureDomallInstalled();
		}
		List<CathDomain> matches = new ArrayList<CathDomain>();
		for ( String k : domainMap.keySet() ) {
			if ( domainMap.get(k).getName().startsWith(query) ) {
				matches.add( domainMap.get(k) );
			}
		}
		return matches;
	}

	@Override
	public CathDomain getDescriptionByNodeId(String nodeId) {
		if (useCathDomainDescriptionFile) {
			ensureDomainDescriptionInstalled();
		} else {
			ensureDomallInstalled();
		}
		CathNode node = getCathNode(nodeId);
		return domainMap.get(node.getRepresentative());
	}

	@Override
	public List<CathDomain> getDomainsForPdb(String pdbId) {
		if (useCathDomainDescriptionFile) {
			ensureDomainDescriptionInstalled();
		} else {
			ensureDomallInstalled();
		}

	  // cath IDs in lower case...
		return pdbMap.get(pdbId.toLowerCase());
	}

	@Override
	public CathDomain getDomainByCathId(String cathId) {
		if (useCathDomainDescriptionFile) {
			ensureDomainDescriptionInstalled();
		} else {
			ensureDomallInstalled();
		}
		return domainMap.get(cathId);
	}

	@Override
	public CathDomain getDescriptionByCathId(String cathId) {
		if (useCathDomainDescriptionFile) {
			ensureDomainDescriptionInstalled();
		} else {
			ensureDomallInstalled();
		}
		return domainMap.get(cathId);
	}

	@Override
	public List<CathDomain> getDomainsByNodeId(String nodeId) {
		if (useCathDomainDescriptionFile) {
			ensureDomainDescriptionInstalled();
		} else {
			ensureDomallInstalled();
		}
		List<CathDomain> domains = new ArrayList<CathDomain>();
		for (String domainName : domainMap.keySet()) {
			CathDomain description = domainMap.get(domainName);
			if ( description.getCATH().startsWith(nodeId) ) {
				domains.add(description);
			}
		}
		return domains;
	}

	@Override
	public List<CathFragment> getFragmentsByPdbId(String pdbId) {
		if ( useCathDomainDescriptionFile || !parseCathFragments ) return null;
		ensureDomallInstalled();
		return fragmentMap.get(pdbId);
	}

	private void parseCathDomainList() throws IOException {
		File file = new File(getDomainListFileName());
		InputStreamProvider ips = new InputStreamProvider();
		BufferedReader buffer = new BufferedReader (new InputStreamReader(ips.getInputStream(file)));
		parseCathDomainList(buffer);
	}

	private void parseCathDomainList(BufferedReader bufferedReader) throws IOException{
		String line;
	 //   int counter = 0;
		while ( (line = bufferedReader.readLine()) != null ) {
			if ( line.startsWith("#") ) continue;
			CathDomain cathDomain = parseCathListFileLine(line);
		   // counter++;

			String pdbId = cathDomain.getPdbIdAndChain().substring(0,4); // includes chain letter

			List<CathDomain> domainList;
			if ( pdbMap.containsKey(pdbId)){
				domainList = pdbMap.get(pdbId);
			} else {
				domainList = new ArrayList<CathDomain>();
				pdbMap.put(pdbId,domainList);
			}

			domainList.add(cathDomain);

			domainMap.put( cathDomain.getDomainName(), cathDomain );
		}
	}

	private void parseCathNames() throws IOException {
		File file = new File(getNodeListFileName());
		InputStreamProvider ips = new InputStreamProvider();
		BufferedReader buffer = new BufferedReader (new InputStreamReader(ips.getInputStream(file)));
		parseCathNames(buffer);
	}

	private void parseCathNames(BufferedReader bufferedReader) throws IOException{
		String line;
		//int counter = 0;
		while ( (line = bufferedReader.readLine()) != null ) {
			if ( line.startsWith("#") ) continue;
			CathNode cathNode = parseCathNamesFileLine(line);
			cathTree.put(cathNode.getNodeId(), cathNode);
		}
	}

	private void parseCathDomainDescriptionFile() throws IOException {
		File file = new File(getDomainDescriptionFileName());
		InputStreamProvider ips = new InputStreamProvider();
		BufferedReader buffer = new BufferedReader (new InputStreamReader(ips.getInputStream(file)));
		parseCathDomainDescriptionFile(buffer);
	}

	private void parseCathDomainDescriptionFile(BufferedReader bufferedReader) throws IOException{
		String line;
		DateFormat dateFormat = new SimpleDateFormat("dd-MMM-yyyy");
		//int counter = 0;
		CathDomain cathDescription = null; //TODO initialize these or catch NPE
		StringBuilder name = null;
		StringBuilder source = null;
		StringBuilder seqh = null;
		StringBuilder seqs = null;
		List<CathSegment> segments = null;
		CathSegment segment = null;
		StringBuilder sseqh = null;
		StringBuilder sseqs = null;
		while ( (line = bufferedReader.readLine()) != null ) {
			if ( line.startsWith("#") ) continue;
			if ( line.startsWith("FORMAT") ) {
				cathDescription = new CathDomain();
				cathDescription.setFormat( line.substring(10) );

				name = new StringBuilder();
				source = new StringBuilder();
				seqh = new StringBuilder();
				seqs = new StringBuilder();

			} else if ( line.startsWith("DOMAIN") ) {
				cathDescription.setDomainName( line.substring(10) );
			} else if ( line.startsWith("VERSION") ) {
				cathDescription.setVersion( line.substring(10) );
			} else if ( line.startsWith("VERDATE") ) {
				try {
					cathDescription.setDate( dateFormat.parse( line.substring(10) ) );
				} catch (ParseException e) {
					LOGGER.error(e.getMessage(), e);
				}
			} else if ( line.startsWith("NAME") ) {
				name.append( line.substring(10) );
			} else if ( line.startsWith("SOURCE") ) {
				source.append( line.substring(10) );
			} else if ( line.startsWith("CATHCODE") ) {
				cathDescription.setCATH( line.substring(10) );
			} else if ( line.startsWith("DLENGTH") ) {
				cathDescription.setLength( Integer.parseInt( line.substring(10) ) );
			} else if ( line.startsWith("DSEQH") ) {
				seqh.append( line.substring(10) );
			} else if ( line.startsWith("DSEQS") ) {
				seqs = seqs.append( line.substring(10) );
			} else if ( line.startsWith("NSEGMENTS") ) {
				segments = new ArrayList<CathSegment>();
			} else if ( line.startsWith("SEGMENT") ) {
				segment = new CathSegment();
				sseqh = new StringBuilder();
				sseqs = new StringBuilder();
			} else if ( line.startsWith("SRANGE") ) {
				int startStart = line.indexOf("=",10) + 1;
				int startStop = line.indexOf(" ",10);
				int stopStart = line.indexOf("=",startStop) + 1;
//                Integer start = Integer.parseInt( line.substring(startStart,startStop) );
//                Integer stop = Integer.parseInt( line.substring(stopStart, line.length()) );
				segment.setStart( line.substring(startStart,startStop) );
				segment.setStop( line.substring(stopStart) );
			} else if ( line.startsWith("SLENGTH") ) {
				segment.setLength( Integer.parseInt( line.substring(10) ) );
			} else if ( line.startsWith("SSEQH") ) {
				sseqh.append( line.substring(10) );
			} else if ( line.startsWith("SSEQS") ) {
				sseqs.append( line.substring(10) );
			} else if ( line.startsWith("ENDSEG") ) {
				segments.add( segment );
				segment.setSegmentId( segments.size() );
				segment.setSequenceHeader( sseqh.toString() );
				segment.setSequence( sseqs.toString() );
			} else if ( line.startsWith("//") ) {
				cathDescription.setName( name.toString() );
				cathDescription.setSource( source.toString() );
				cathDescription.setSequenceHeader( seqh.toString() );
				cathDescription.setSequence( seqs.toString() );
				cathDescription.setSegments(segments);
				//counter++;

				String pdbId = cathDescription.getPdbIdAndChain().substring(0,4); // includes chain letter
				List<CathDomain> domainList;
				if ( pdbMap.containsKey(pdbId)){
					domainList = pdbMap.get(pdbId);
				} else {
					domainList = new ArrayList<CathDomain>();
					pdbMap.put(pdbId,domainList);
				}

				domainList.add(cathDescription);

				domainMap.put( cathDescription.getDomainName(), cathDescription );

			}
		}
//        transposeDomainData();
	}

/*    private void transposeDomainData() {
		ensureDomainListInstalled();
		for (String k : domainMap.keySet() ) {
			cathMap.get(k).getDomain().setResolution(domainMap.get(k).getResolution());
			cathMap.get(k).getDomain().setSOLID(domainMap.get(k).getSOILD());
		}
	}*/

	private CathDomain parseCathListFileLine(String line) {
		CathDomain cathDomain = new CathDomain();
		String [] token = line.split("\\s+");
		cathDomain.setDomainName(token[0]);
		cathDomain.setClassId(Integer.parseInt(token[1]));
		cathDomain.setArchitectureId(Integer.parseInt(token[2]));
		cathDomain.setTopologyId(Integer.parseInt(token[3]));
		cathDomain.setHomologyId(Integer.parseInt(token[4]));
		cathDomain.setSequenceFamilyId(Integer.parseInt(token[5]));
		cathDomain.setOrthologousSequenceFamilyId(Integer.parseInt(token[6]));
		cathDomain.setLikeSequenceFamilyId(Integer.parseInt(token[7]));
		cathDomain.setIdenticalSequenceFamilyId(Integer.parseInt(token[8]));
		cathDomain.setDomainCounter(Integer.parseInt(token[9]));
		cathDomain.setLength(Integer.parseInt(token[10]));
		cathDomain.setResolution(Double.parseDouble(token[11]));
		return cathDomain;
	}

	private CathNode parseCathNamesFileLine(String line) {
		CathNode cathNode = new CathNode();
		String[] token = line.split("\\s+",3);
		cathNode.setNodeId( token[0] );
		int idx = token[0].lastIndexOf(".");
		if ( idx == -1 ) idx = token[0].length();
		cathNode.setParentId( token[0].substring( 0, idx ) );
		cathNode.setRepresentative( token[1] );
		cathNode.setDescription( token[2].replace(":","") );
		return cathNode;
	}

	private void parseCathDomall() throws IOException{
		File file = new File(getDomallFileName());
		InputStreamProvider ips = new InputStreamProvider();
		BufferedReader buffer = new BufferedReader (new InputStreamReader(ips.getInputStream(file)));
		parseCathDomall(buffer);
	}

	private void parseCathDomall(BufferedReader bufferedReader) throws IOException{
		String line;
		while ( ((line = bufferedReader.readLine()) != null) ) {
			if ( line.startsWith("#") ) continue;
			if ( line.length() == 0 ) continue;
			String[] token = line.split("\\s+");
			String chainId = token[0];
			Integer numberOfDomains = Integer.parseInt( token[1].substring(1) );
			Integer numberOfFragments = Integer.parseInt( token[2].substring(1) );
			int domIdx = 3;
			int segIdx;
			Integer sstop;
			Integer sstart;
			Integer fstart;
			Integer fstop;
			Integer flength;
			for (int i=1; i<=numberOfDomains; i++) {
				DecimalFormat df = new DecimalFormat("00");
				String domainId;
				CathDomain domain;

//                This logic is necessary because singular domains may be labeled with 00 or 01.
//                If there is more than one domain, they are always numbered from 01.
				if (numberOfDomains==1) {
					domainId = chainId + "00";
					domain = domainMap.get(domainId);
					if (domain==null) {
						domainId = chainId + "01";
						domain = domainMap.get(domainId);
					}
				} else {
					domainId = chainId + df.format(i);
					domain = domainMap.get(domainId);
				}

				Integer numberOfSegments = Integer.parseInt( token[domIdx] );

				if ( domain == null ) {
					domIdx += 6*numberOfSegments + 1;
					continue;
				}

				List<CathSegment> segments = new ArrayList<CathSegment>(numberOfSegments);
				segIdx = 1; // Offset from domIdx.
				for (int j=1; j<=numberOfSegments; j++) {
					CathSegment segment = new CathSegment();
					segment.setSegmentId(j);
//                    String chainLetter = token[domIdx+segIdx]; // Redundant unless some domains cross chain boundaries.
					sstart = Integer.parseInt( token[domIdx + segIdx + 1] );
					String sstartInsertion = token[domIdx + segIdx + 2];
					sstartInsertion = sstartInsertion.equals("-") ? "" : sstartInsertion;
//                    String chainLetter = token[domIdx+segIdx+4]; // Redundant unless some segments cross chain boundaries.
					segment.setStart(sstart + sstartInsertion);

					sstop = Integer.parseInt( token[domIdx + segIdx + 4] );
					String sstopInsertion = token[domIdx + segIdx + 5];
					sstopInsertion = sstopInsertion.equals("-") ? "" : sstopInsertion;

					segment.setStart(sstart + sstartInsertion);
					segment.setStop(sstop + sstopInsertion);
					segment.setLength(1 + sstop - sstart);
					segments.add(segment);

					segIdx += 6;
				}
				domain.setSegments(segments);
				domIdx += 6*numberOfSegments + 1;
			}
			if (parseCathFragments) {
			List<CathFragment> fragments = new ArrayList<CathFragment>(numberOfFragments);
				for (int i=1; i<=numberOfFragments; i++) {
					CathFragment fragment = new CathFragment();
					fragment.setFragmentId(i);
//                    String chainLetter = token[domIdx]; // Redundant unless some fragments cross chain boundaries.
					fstart = Integer.parseInt( token[domIdx+1] );
					String fstartInsertion = token[domIdx + 2];
					fstartInsertion = fstartInsertion.equals("-") ? "" : fstartInsertion;
					fragment.setStart(fstart + fstartInsertion);
//                    String chainLetter = token[domIdx+3]; // Redundant unless some fragments cross chain boundaries.
					fstop = Integer.parseInt( token[domIdx+4] );
					String fstopInsertion = token[domIdx + 5];
					fstopInsertion = fstopInsertion.equals("-") ? "" : fstopInsertion;
					fragment.setStop(fstop + fstopInsertion);
					flength = Integer.parseInt( token[domIdx + 6].replaceAll("[^0-9]","") );
					fragment.setLength(flength);
					fragments.add(fragment);
					domIdx += 7;
				}
				fragmentMap.put(chainId, fragments);
			}
//            if ( domIdx != token.length ); // Problems.
		}
	}

	protected void downloadFileFromRemote(URL remoteURL, File localFile) throws IOException{
//        System.out.println("downloading " + remoteURL + " to: " + localFile);

		long timeS = System.currentTimeMillis();
		File tempFile  = File.createTempFile(FileDownloadUtils.getFilePrefix(localFile), "."+ FileDownloadUtils.getFileExtension(localFile));

		FileOutputStream out = new FileOutputStream(tempFile);

		InputStream in = remoteURL.openStream();
		byte[] buf = new byte[4 * 1024]; // 4K buffer
		int bytesRead;
		while ((bytesRead = in.read(buf)) != -1) {
			out.write(buf, 0, bytesRead);
		}
		in.close();
		out.close();

		FileDownloadUtils.copy(tempFile,localFile);

		// delete the tmp file
		tempFile.delete();

		long size =  localFile.length();

		double disp = size / 1024.0;
		String unit = " kB";
		if ( disp > 1024 ) {
			unit = " MB";
			disp = disp / 1024.0;
		}
		long timeE = System.currentTimeMillis();
		LOGGER.info("Downloaded file {} ({}) to local file {} in {} sec.", remoteURL, String.format("%.1f",disp) + unit, localFile, (timeE - timeS)/1000);
	}

	private boolean domainDescriptionFileAvailable(){
		String fileName = getDomainDescriptionFileName();
		File f = new File(fileName);
		return f.exists();
	}

	private boolean domainListFileAvailable(){
		String fileName = getDomainListFileName();
		File f = new File(fileName);
		return f.exists();
	}

	private boolean nodeListFileAvailable(){
		String fileName = getNodeListFileName();
		File f = new File(fileName);
		return f.exists();
	}

	private boolean domallFileAvailable() {
		String fileName = getDomallFileName();
		File f= new File(fileName);
		return f.exists();
	}

	protected void downloadDomainListFile() throws IOException{
		String remoteFilename = domainListFileName;
		URL url = new URL(buildUrl(remoteFilename)); 
		String localFileName = getDomainListFileName();
		File localFile = new File(localFileName);
		downloadFileFromRemote(url, localFile);
	}

	protected void downloadDomainDescriptionFile() throws IOException{
		String remoteFilename = domainDescriptionFileName;
		URL url = new URL(buildUrl(remoteFilename));
		String localFileName = getDomainDescriptionFileName();
		File localFile = new File(localFileName);
		downloadFileFromRemote(url, localFile);
	}

	protected void downloadNodeListFile() throws IOException{
		String remoteFilename = nodeListFileName;
		URL url = new URL(buildUrl(remoteFilename));
		String localFileName = getNodeListFileName();
		File localFile = new File(localFileName);
		downloadFileFromRemote(url, localFile);
	}

	protected void downloadDomallFile() throws IOException {
		String remoteFileName = domallFileName;
		URL url = new URL(buildUrl(remoteFileName));
		String localFileName = getDomallFileName();
		File localFile = new File(localFileName);
		downloadFileFromRemote(url, localFile);
	}

	public void ensureDomainListInstalled(){
		if ( installedDomainList.get() ) return;

		if ( ! domainListFileAvailable() ){
			try {
				downloadDomainListFile();
			} catch (Exception e){
				LOGGER.error("Could not download CATH domain list file. Error: {}", e.getMessage());
				installedDomainList.set(false);
				return;
			}
		}

		try {
			parseCathDomainList();
		} catch (Exception e){
			LOGGER.error(e.getMessage(), e);
			installedDomainList.set(false);
			return;
		}
		installedDomainList.set(true);
	}

	public void ensureDomainDescriptionInstalled(){
		if ( installedDomainDescription.get() ) return;

		if ( ! domainDescriptionFileAvailable() ){
			try {
				downloadDomainDescriptionFile();
			} catch (Exception e){
				LOGGER.error("Could not download CATH domain description file. Error: {}", e.getMessage());
				installedDomainDescription.set(false);
				return;
			}
		}

		try {
			parseCathDomainDescriptionFile();
		} catch (Exception e){
			LOGGER.error(e.getMessage(), e);
			installedDomainDescription.set(false);
			return;
		}
		installedDomainDescription.set(true);
	}

	public void ensureNodeListInstalled(){
		if ( installedNodeList.get() ) return;

		if ( ! nodeListFileAvailable() ){
			try {
				downloadNodeListFile();
			} catch (Exception e){
				LOGGER.error("Could not download CATH node list file. Error: {}", e.getMessage());
				installedNodeList.set(false);
				return;
			}
		}

		try {
			parseCathNames();
		} catch (Exception e){
			LOGGER.error(e.getMessage(), e);
			installedNodeList.set(false);
			return;
		}
		installedNodeList.set(true);
	}

	public void ensureDomallInstalled() {
		ensureDomainListInstalled();

		if ( !installedDomainList.get() ) {
			installedDomall.set(false);
			return;
		}

		if ( installedDomall.get() ) return;

		if ( ! domallFileAvailable() ){
			try {
				downloadDomallFile();
			} catch (Exception e) {
				LOGGER.error("Could not download CATH domain all file. Error: {}", e.getMessage());
				installedDomall.set(false);
				return;
			}
		}

		try {
			parseCathDomall();
		} catch (Exception e) {
			LOGGER.error(e.getMessage(), e);
			installedDomall.set(false);
			return;
		}
		installedDomall.set(true);
	}

	public void setCathVersion(String cathVersion) {
		this.cathVersion = cathVersion;
	}
	

}
