/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: LibDirs.java
 *
 * Copyright (c) 2003, Static Free Software. All rights reserved.
 *
 * Electric(tm) 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.
 *
 * Electric(tm) 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 com.sun.electric.tool.io.input;

import javax.swing.filechooser.FileSystemView;
import javax.swing.filechooser.FileView;
import javax.swing.*;
import java.io.File;
import java.io.FileReader;
import java.io.BufferedReader;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.FileNotFoundException;
import java.util.*;

/**
 * Class for reading a text file that specifies
 * additional library directories from which to
 * read in library (elib) files.
 *
 * <p>
 * LibDirs files have the following syntax:
 * <p>
 * * <comment text>
 * <p>
 * include <lib_dirs_file>
 * <p>
 * <library_directory>
 *
 * <p>
 */
public class LibDirs {

    /** Default LibDirs file name */                        private static String libDirsFile = "LIBDIRS";
    /** List of library directories from LibDirs file*/     private static ArrayList<String> dirs = new ArrayList<String>();
    /** List of libDirsFiles read (prevent recursion) */    private static ArrayList<String> libDirsFiles = new ArrayList<String>();

    /** Creates a new instance of LibDirs */
    LibDirs() {
    }

    /** return list of Lib Dirs 
     * @return ArrayList of lib dirs
     */
    public static synchronized Iterator<String> getLibDirs() {
        ArrayList<String> list = new ArrayList<String>(dirs);
        return list.iterator();
    }

    /**
     * Read in LibDirs file.
     * @param dir the directory that may contain a LIBDIRS file
     * @return true on error, or no LIBDIRS file found.
     */
    public static synchronized boolean readLibDirs(String dir)
    {
        if (dir == null) return true;
        String libDirFile = dir + File.separator + libDirsFile;
        // read current working dir first, if set to do so
        boolean error = false;
        dirs.clear();
        libDirsFiles.clear();

        if (parseFile(libDirFile)) error = true;
        return error;
    }
    
    /**
     * Get all files with a certain extension in the libdirs
     * @param extension the desired filename end (include period for extensions)
     * @return a List of matching files
     */
    public static synchronized List<File> getExt(final String extension) {
    	Iterator<String> libDirs = getLibDirs();
    	List<File> extFileList = new ArrayList<File>();
    	
		// define a filter for files that end in extension
		FilenameFilter extFilter = new FilenameFilter() {
			public boolean accept(File dir, String name) {
				return name.endsWith(extension);
			}
		};
		// use filter to find all files in the libdirs that have desired extension
		while (libDirs.hasNext()) {
			File dir = new File(libDirs.next());
			File[] extFiles = dir.listFiles(extFilter);
			if (extFiles == null) continue; // for whatever reason, the list of directories is not right
			for (File extFile : extFiles) {
				extFileList.add(extFile);
			}
		}
		return extFileList;
    }
        
    /** 
     * parse a LIF file and read in libraries specified.
     * @return true on error.
     */
    private static boolean parseFile(String fileName)
    {
        if (fileName == null) return true;
        
        BufferedReader in = null;
        File file = new File(fileName);
		try {
            FileReader fr = new FileReader(file);
            in = new BufferedReader(fr);
		} catch (FileNotFoundException e) {
			//System.out.println("Could not find file: " + fileName);
			return true;
		}
        libDirsFiles.add(file.getAbsolutePath());

        // parse file
        boolean error = false;
        int lineNumber = 0;
        try {
            String line;
            while ( (line = in.readLine()) != null) {
                if (parseLine(line, file)) {
                    System.out.println("Parse error: "+fileName+":"+lineNumber);
                    error = true;
                    lineNumber++;
                }
            }
            in.close();
        } catch (IOException e) {
            error = true;
//            System.out.println("IOError "+e.getMessage());
        }
        return error;
    }
    
    /**
     * Parse one line of LIF file.
     * @return true on error.
     */
    private static boolean parseLine(String line, File libdirFile)
    {
        line = line.trim();                             // remove leading/trailing whitespace
        if (line.isEmpty()) return false; 				// empty line
        String[] words = line.split("\\s+");            // split by whitespace
        if (words.length <= 0) return false;            // nothing on line
        if (words[0].equals("*")) return false;         // comment
        if (words[0].equals("include")) {               // include statement
            if (words.length != 2) return true;
            // prevent recursive includes
            File f = new File(words[1]);
            if (libDirsFiles.contains(f.getAbsolutePath())) return true;
            // read included LibDir file
            return parseFile(words[1]);
        }
        // add dir to list, prepend current dir if relative path
        String dir = words[0];
        if (dir.startsWith(".")) {
            dir = libdirFile.getParentFile().getAbsolutePath() + File.separator + dir;
        }
        dirs.add(dir);
        return false;
    }

    // --------------------------------------------------------------

    /**
     * Allow JFileChooser to see libdir references as files. This should only
     * be used when trying to open JELIB/ELIB libraries through a JFileChooser.
     * We can't extend UnixFileSystemView or WindowsFileSystemView because they
     * are private classes, so instead we must wrap them.
     */
    public static class LibDirFileSystemView extends FileSystemView {

        private final FileSystemView osView;        // at run time java determines an OS-specific view
        private final HashMap<String,File> libFiles;             // key: absolute path; value: File

        public LibDirFileSystemView(FileSystemView osView) {
            this.osView = osView;
            libFiles = new HashMap<String,File>();
        }

        public File createFileObject(File dir, String filename) { return osView.createFileObject(dir, filename); }
        public File createFileObject(String path) { return osView.createFileObject(path); }
        protected File createFileSystemRoot(File f) {
            throw new Error("Unsupported operation"); // can't access protected method of osView
        }
        public File createNewFolder(File containingDir) throws IOException {
            return osView.createNewFolder(containingDir);
        }
        public File getChild(File parent, String fileName) { return osView.getChild(parent, fileName); }
        public File getDefaultDirectory() { return osView.getDefaultDirectory(); }

        public synchronized File [] getFiles(File dir, boolean useFileHiding) {
            File [] usual = osView.getFiles(dir, useFileHiding);
            libFiles.clear();

            if (LibDirs.readLibDirs(dir.getPath())) {
                return usual;
            }
            // amalgamate all of the files
            ArrayList<File> alllibfiles = new ArrayList<File>();
            if (usual.length != 0) alllibfiles.addAll(Arrays.asList(usual));

            for (Iterator<String> it = LibDirs.getLibDirs(); it.hasNext(); ) {
                String str = it.next();
                File libdir = new File(str);
                if (!libdir.exists()) continue;
                if (!libdir.isDirectory()) continue;
                File [] files = osView.getFiles(libdir, useFileHiding);
                //System.out.println("Reading files from dir "+str+", found "+files.length+" files.");
                if (files.length == 0) continue;
                alllibfiles.addAll(Arrays.asList(files));
                for (int i=0; i<files.length; i++) {
                    libFiles.put(files[i].getAbsolutePath(), files[i]);
                }
            }
            File [] all = new File[alllibfiles.size()];
            for (int i=0; i<alllibfiles.size(); i++) {
                File f = alllibfiles.get(i);
                all[i] = f;
            }
            return all;
        }
        public synchronized boolean isLibFile(File f) {
            if (f == null) return false;
            if (libFiles.containsKey(f.getAbsolutePath())) return true;
            return false;
        }

        public File getHomeDirectory() { return osView.getHomeDirectory(); }
        public File getParentDirectory(File dir) { return osView.getParentDirectory(dir); }
        public File [] getRoots() { return osView.getRoots(); }
        public String getSystemDisplayName(File f) { return osView.getSystemDisplayName(f); }
        public Icon getSystemIcon(File f) { return osView.getSystemIcon(f); }
        public String getSystemTypeDescription(File f) { return osView.getSystemTypeDescription(f); }
        public boolean isComputerNode(File dir) { return osView.isComputerNode(dir); }
        public boolean isDrive(File dir) { return osView.isDrive(dir); }
        public boolean isFileSystem(File f) { return osView.isFileSystem(f); }
        public boolean isFileSystemRoot(File dir) { return osView.isFileSystemRoot(dir); }
        public boolean isFloppyDrive(File dir) { return osView.isFloppyDrive(dir); }
        public boolean isHiddenFile(File f) { return osView.isHiddenFile(f); }
        public boolean isParent(File folder, File file) {
            // all lib files should look like they are in the current dir
            if (isLibFile(file)) { return true; }
            return osView.isParent(folder, file);
        }
        public boolean isRoot(File f) { return osView.isRoot(f); }
        public Boolean isTraversable(File f) { return osView.isTraversable(f); }
    }

    public static LibDirFileSystemView newLibDirFileSystemView(FileSystemView osView) { return new LibDirFileSystemView(osView); }
    
    /**
     * Allow File Chooser to see libdir references as files.
     */
    public static class LibDirFileView extends FileView {
        private LibDirFileSystemView view;
        public LibDirFileView(LibDirFileSystemView view) {
            this.view = view;
        }

        public String getName(File f) {
            if (f == null) return null;
            if (view.isLibFile(f)) return f.getName() + " [REF]";
            return null;
        }
    }
}
