package jp.sourceforge.acerola3d.a3editor;

import java.io.*;
import java.net.MalformedURLException;
import java.nio.channels.FileChannel;
import java.util.*;

/**
 * ファイルマネージャというか，A3Editorで必要となる
 * ファイルの管理機能を実現するクラス．実現する機能は以下．
 * 
 * 1. rootDirを受け取り，その中のファイルの一覧を保持する
 * 2. ファイルタイプを自動識別する．
 * 3. 必要があればユーザがファイルタイプを簡単に変更できる
 * 4. 保持しているファイル一覧の情報を取り出すAPIを持っている．
 * 5. 2の実現のためにファイルの一覧を表示すると同時にファイル
 *    タイプ変更のためのGUIを提供する．
 * 6. refresh()でファイルシステム上の更新反映させたり
 *    setRootDir()で別の作業ディレクトリのセットが簡単にできる．
 * 7. ついでにGUIからは，各種ファイルのビューアを起動できる
 *    ようにしておく．
 */
class A3eFileManager {
    File rootDir;
    A3eFile a3eRootDir;
    A3eFile files[];
    A3eFileManagerGUI fileManagerGUI;

    /**
     * rootDirで指定したディレクトリの中のファイルを
     * 管理するためのA3FileManagerを生成します．
     */
    A3eFileManager(File rootDir) throws IOException {
        this.rootDir = rootDir;
        refresh();
        fileManagerGUI = new A3eFileManagerGUI(this);
    }
    /**
     *
     */
    void setRootDir(File rootDir) throws IOException {
        this.rootDir = rootDir;
        refresh();
    }
    /**
     *
     */
    void refresh() throws IOException {
        ArrayList<A3eFile> filesAL = new ArrayList<A3eFile>();
        a3eRootDir = new A3eFile(this,null,"");
        for (File f:rootDir.listFiles()) {
            refreshR(a3eRootDir,f,filesAL);
        }
        A3eFile oldFiles[] = files;
        files = filesAL.toArray(new A3eFile[0]);
        if (oldFiles!=null) {
            for (A3eFile newF:files) {
                for (A3eFile oldF:oldFiles) {
                    if (newF.getPath().equals(oldF.getPath())) {
                        newF.isIgnored = oldF.isIgnored;
                        newF.type = oldF.type;
                    }
                }
            }
        }

        //CATALOGファイルのチェック
        int catalogCount = 0;
        for (A3eFile f:files)
            if (f.type==A3eFileType.CATALOG)
                catalogCount++;
        //CATALOGファイルが0なら生成してもう一度refresh()
        if (catalogCount==0) {
            File f = new File(rootDir,"CATALOG.XML");
            //WindowsのファイルシステムでもMacのファイルシステムでも
            //CATALOG.XMLとCATALOG.xmlは同一ファイルと判定されて
            //しまうみたいで、すごくわかりずらいエラーになる。
            //しょうがないので、これの対策をfixCatalogFilename()
            //で処理することにする。
            if (f.exists()) {
                fixCatalogFilename(f);
            }
            f.createNewFile();
            refresh();
        } else if (catalogCount>=2) {
            //CATALOGファイル数>=2なら警告
            System.out.println("警告：2以上のCATALOG.XMLファイルがあります．");
        }
    }
    /*
     * refresh()実現のための再起用のメソッド
     */
    void refreshR(A3eFile parent,File f,ArrayList<A3eFile> filesAL) {
        if (!f.isDirectory()) {
            A3eFile af = new A3eFile(this,parent,f.getName());
            filesAL.add(af);
            parent.children.add(af);
        } else {
            A3eFile af = new A3eFile(this,parent,f.getName());
            filesAL.add(af);
            parent.children.add(af);
            for (File ff:f.listFiles()) {
                refreshR(af,ff,filesAL);
            }
        }
    }
    /**
     * 最終的なAcerola3Dファイルに含めるべき
     * 全てのファイル(フォルダ)の一覧を返します．
     */
    String[] list() {
        ArrayList<String> ret = new ArrayList<String>();
        for (A3eFile f:files) {
            if (!f.isIgnored)
                ret.add(f.getPath());
        }
        return ret.toArray(new String[0]);
    }
    /**
     * 最終的なAcerola3Dファイルに含めるべき
     * 全てのファイルのなかで，指定したファイル
     * タイプの物の一覧を返します．
     */
    String[] list(A3eFileType t) {
        ArrayList<String> ret = new ArrayList<String>();
        for (A3eFile f:files) {
            if (f.type == t) {
                if (!f.isIgnored)
                    ret.add(f.getPath());
            }
        }
        return ret.toArray(new String[0]);
    }

    A3eFile[] listA3eFile() {
        return files.clone();
    }

    A3eFile[] listA3eFile(A3eFileType t) {
        ArrayList<A3eFile> ret = new ArrayList<A3eFile>();
        for (A3eFile f:files) {
            if (f.type == t) {
                if (!f.isIgnored)
                    ret.add(f);
            }
        }
        return ret.toArray(new A3eFile[0]);
    }

    A3eFileManagerGUI getGUI() {
        return fileManagerGUI;
    }
    A3eFile getRefreshedFile(A3eFile oldFile) {
        A3eFile ret = null;
        for (A3eFile f:files) {
            if (oldFile.getPath().equals(f.getPath())) {
                ret = f;
                break;
            }
        }
        return ret;
    }
    A3eFile getA3eFileFromPath(String path) {
        A3eFile ret = null;
        for (A3eFile f : listA3eFile()) {
            if (f.getPath().equals(path)) {
                ret = f;
                break;
            }
        }
        return ret;
    }
    String path2FileName(String filePath) {
        //TODO これは必要かな?
        A3eFile f = getA3eFileFromPath(filePath);
        if (f!=null)
            return f.getPath();
        return null;
    }
    /**
     * ファイルタイプがtでファイル名がfileNameのファイルのパスの一覧を返します。
     * tがnullの時は全てのファイルタイプから一覧を作成します。
     */
    String[] fileName2Path(String fileName,A3eFileType t) {
        A3eFile files[] = null;
        if (t==null)
            files = listA3eFile();
        else
            files = listA3eFile(t);
        ArrayList<String> al = new ArrayList<String>();
        for (A3eFile f : files) {
            if (f.name.equals(fileName))
                al.add(f.getPath());
        }
        return al.toArray(new String[0]);
    }
    String path2UrlString(String path) {
        A3eFile f = getA3eFileFromPath(path);
        if (f==null)
            return null;
        try {
            return f.toFile().toURI().toURL().toString();
        } catch (MalformedURLException e) {
            // ありえないはず。
            return null;
        }
    }
    //WindowsとMacのファイルシステムではCATALOG.XMLとCATALOG.xml
    //は同じファイルとみなされるみたいで面倒なことになっている。
    //とりあえず以下の方法で対処。なんか納得かない。
    void fixCatalogFilename(File f) throws IOException {
        //とりあえずバックアップ
        File orgFile = f;
        File bakFile = new File(orgFile.getParentFile(),orgFile.getName()+".bak");
        FileInputStream fis = new FileInputStream(orgFile);
        FileOutputStream fos = new FileOutputStream(bakFile);
        FileChannel orgChannel = fis.getChannel();
        FileChannel bakChannel = fos.getChannel();
        try {
            orgChannel.transferTo(0, orgChannel.size(), bakChannel);
        } finally {
            orgChannel.close();
            bakChannel.close();
        }
        //オリジナルファイルの削除
        if (!f.delete())
            throw new IOException();
        //バックアップを正し名前のファイルにコピー
        orgFile = bakFile;
        bakFile = new File(f.getParentFile(),"CATALOG.XML");
        fis = new FileInputStream(orgFile);
        fos = new FileOutputStream(bakFile);
        orgChannel = fis.getChannel();
        bakChannel = fos.getChannel();
        try {
            orgChannel.transferTo(0, orgChannel.size(), bakChannel);
        } finally {
            orgChannel.close();
            bakChannel.close();
        }
    }
}
