/*
 * HFile.java
 * 
 * Author: http://www.hrtdotnet.jp/
 *
 */
package jp.hrtdotnet.java.io;

import java.io.*;

/**
 * ファイルの操作をするクラスです。
 * このクラスを利用してディレクトリを操作することは禁止されています。
 * 
 * @version 2.0
 * @author hrtdotnet.jp
 * <br>http://www.hrtdotnet.jp/
 * @since 2.0
 */
public class HFile extends File {
    
    private static final long serialVersionUID = -8719547187991724396L;
    private static final String DOT = ".";

    /**
     * コンストラクタ。
     * 
     * @param filePath ファイルパス
     * @throws IOException filePathが不正な場合
     * @since 2.0
     */
    public HFile(String filePath)  throws IOException { 
        super(filePath);
        this.constructor();
    }

    /**
     * コンストラクタ。
     * 
     * @param parent ディレクトリパス
     * @param child ファイル名
     * @throws IOException 指定したファイルが不正な場合
     * @since 2.0
     */
    public HFile(String parent, String child) throws IOException {
        super(parent, child);
        this.constructor();
    }

    /**
     * オブジェクトに関連したファイルの拡張子を取得します。
     * 拡張子がないファイルに関しては空文字を取得します。
     * 
     * @return 拡張子
     * @since 2.0
     */
    public String getExtension() {
        String fileName = getName();
        return getExtension(fileName);
    }


    /**
     * ファイルオブジェクトと同じパスのファイルが存在する場合、
     * 「ファイルパス」+「エイリアス」+「指定桁数分のシーケンス番号 」+「拡張子」の文字列を取得します。
     * 同名のファイルが存在しない場合はファイルオブジェクトのファイルパスを取得します。
     * <br>
     * なおオブジェクト自身のファイルパスを変更するわけではありません。
     * 
     * @param alias エイリアス
     * @param digit シーケンス番号分の桁数
     * @return 「ファイル名」+「エイリアス」+「指定桁数分のシーケンス番号」+「拡張子の文字列」
     * @since 2.0
     */
    public String getOtherFileName(String alias, int digit) {

        String extension = getExtension();

        // エイリアスが null もしくは空文字の場合は自動連番機能が有効にはできない。
        // また連番桁数が 0 の場合も自動連番機能が有効にはできないのでそのまま返す。
        if(alias == null || alias.length() < 1 || digit <= 0){
            return getAbsolutePath();
        }        
        
        if (exists()) {
            String oldFileName = null;
            
            if(extension.length() > 0){            
                oldFileName = getAbsolutePath().substring(0, getAbsolutePath().length() - (extension.length() + 1));
            } else {
                oldFileName = getAbsolutePath();
            }

            String strNum = alias;
            
            for (int i = 0; i < digit; i++) {
                strNum += "0";
            }
            String newFileName = oldFileName + strNum + "." + extension;

            if (new File(newFileName).exists()) {
                int num = (int) Math.pow(10, digit);
                for (int i = 1; i < num; i++) {

                    strNum = alias;
                    newFileName = oldFileName;
                    for (int j = digit - 1; j > 0; j--) {
                        if (i / (int) Math.pow(10, j) == 0) {
                            strNum += "0";
                        } else {
                            break;
                        }
                    }
                    newFileName += (strNum + i + "." + extension);
                    if (!new File(newFileName).exists()) {
                        break;
                    }
                }
            }
   
            return newFileName;
        } else {
            return getAbsolutePath();
        }
    }

    
    /**
     * ファイルオブジェクトと同じパスのファイルが存在する場合、
     * 「ファイル名」+「エイリアス」+「シーケンス番号」+「拡張子」の文字列を取得します。
     * 同名のファイルが存在しない場合はファイルオブジェクトのファイルパスを取得します。
     * <br>
     * なおオブジェクト自身のファイルパスを変更するわけではありません。
     * 
     * @param alias エイリアス
     * @return 「ファイル名」+「エイリアス」+「シーケンス番号」+「拡張子」の文字列
     * @since 2.0
     */
    public String getOtherFileName(String alias) {

        String extension = getExtension();

        // エイリアスが null もしくは空文字の場合は自動連番機能が有効にはできない
        if (alias == null || alias.length() < 1) {
            return getAbsolutePath();
        }

        // ファイルが存在する場合
        if (exists()) {
            String oldFileName = null;
            if(extension.length() > 0){
                // ファイルオブジェクトに拡張子が存在する場合は拡張子を切り捨て
                oldFileName = getAbsolutePath().substring(0, getAbsolutePath().length() - (extension.length() + 1));
            } else {
                oldFileName = getAbsolutePath();
            }

            int num = 1; // 番号の初期値を 1 に設定
            String strNum = alias + num; // ファイル名に付加する文字列は "alias + 1"

            String newFileName = oldFileName + strNum + "." + extension;
            while (new File(newFileName).exists()) {
                num++;
                strNum = alias + num;
                newFileName = oldFileName + strNum + "." + extension;
            }
   
            return newFileName;

        // ファイルが存在しない場合はそのまま返す。
        } else {
            return getAbsolutePath();
        }
    }
    


    /**
     * ファイル名が不正でない形式かどうかチェックをします。
     * <br><br>
     * 以下の条件に当てはまるとき不正ファイル名と判断します。
     * <ol>
     * <li>null</li>
     * <li>長さが0バイト（空文字）、256バイト以上</li>
     * <li>大小区別せずに頭文字が「CON.」「AUX.」「PRN.」「NUL.」から始まる</li>
     * <li>拡張子を除いた名前が「COM[0-9]」「LPT[0-9]」のもの（[0-9]は0〜9のどれか）</li>
     * <li>「/」「\」「:」「*」「?」「&quot;」「&lt;」「&gt;」「|」を含む</li>
     * <li>末尾が「.」（ドット）</li>
     * </ol>
     * 
     * @param fileName ファイル名
     * @return 正常はtrue、不正はfalse
     * @since 2.0
     */
    public static boolean checkFileName(String fileName){

        // ファイル名がnullはfalseを返す
        if (fileName == null) {
            return false;
        }

        // ファイル名が空文字はfalseを返す
        if (fileName.length() < 1 || fileName.getBytes().length > 255) {
            return false;
        }

        String upperStr = fileName.toUpperCase();
        
        if (upperStr.startsWith("CON.")
                || upperStr.startsWith("AUX.")
                || upperStr.startsWith("PRN.")
                || upperStr.startsWith("NUL.")) {
            return false;
        }

        if(upperStr.matches("COM[0-9]\\..*") 
                || upperStr.matches("LPT[0-9]\\..*")
                || upperStr.matches("COM[0-9]")
                || upperStr.matches("LPT[0-9]")) {
            return false;
        }

        if (upperStr.indexOf("/") != -1 || upperStr.indexOf("\\") != -1
                || upperStr.indexOf(":") != -1 || upperStr.indexOf("*") != -1
                || upperStr.indexOf("?") != -1 || upperStr.indexOf("\"") != -1
                || upperStr.indexOf("<") != -1 || upperStr.indexOf(">") != -1
                || upperStr.indexOf("|") != -1) {
            return false;
        }

        // ファイル名の最後が "." は不正
        if (fileName.endsWith(".")) {
            return false;
        }
        return true;
    }

    
    /**
     * ファイルオブジェクトとそのファイルの拡張子を指定した拡張子に変換します。
     * オブジェクトのみの存在であればオブジェクトの拡張子を変換します。
     * 
     * @param newExtension 変換する拡張子
     * @return 変換が成功すればtrue、失敗すればfalse
     * @throws IllegalArgumentException newExtensionがnull、もしくは「.」(ドット)の場合
     * @since 2.0
     */
    public boolean renameExtension(String newExtension) throws IllegalArgumentException {
        if(newExtension == null || newExtension.endsWith(".")){
            throw new IllegalArgumentException("Arguments are illegal-values.");
        }
        String newFilePath = null;
        String oldFilePath = getAbsolutePath();
        String oldExtension = getExtension();
        if (oldExtension.length() < 1) {
            if (newExtension.length() > 0) {
                newFilePath = oldFilePath + "." + newExtension;                
            } else {
                newFilePath = oldFilePath;
            }
        } else {
            if (newExtension.length() > 0) {
                newFilePath = oldFilePath.substring(0, oldFilePath.length() - oldExtension.length()) + newExtension;
            } else {
                newFilePath = oldFilePath.substring(0, oldFilePath.length() - oldExtension.length() - 1);
            }            
        }
        File newFile = null;
        try {
            newFile = new HFile(newFilePath);
        } catch (IOException e) {
            return false;
        }
        return renameTo(newFile);        
    }
    
    /**
     * このメソッドは利用してはなりません。
     * 利用した場合、File#mkdir()と同じ処理を行います。
     * 
     * @deprecated このメソッドは利用してはなりません。
     * @since 2.0
     */
    public boolean mkdir() {
        return super.mkdir();
    }
    
    /**
     * このメソッドは利用してはなりません。
     * 利用した場合、File#mkdirs()と同じ処理を行います。
     * 
     * @deprecated このメソッドは利用してはなりません。
     * @since 2.0
     */
    public boolean mkdirs() {
        return super.mkdirs();
    }

    /**
     * オブジェクトに関連しているファイルをストリームに出力します。
     * 
     * @param out 出力ストリーム
     * @throws IOException
     *          オブジェクトに関連しているファイルが存在しない、またはストリームのIOに失敗した場合
     * @throws NullPointerException
     *          outがnullの場合
     * @since 2.0
     */
    public void copy(OutputStream out) throws IOException {
        FileInputStream is = null;
        try {
            if (!this.exists()) {
                throw new IOException("The file to have been in this object with the relation doesn't exist.");
            }
            is = new FileInputStream(this);
            copy(is, out);
        } finally {
            if (is != null) {
                is.close();
            }
        }
    }

    /**
     * オブジェクトに関連しているファイルをコピー元としてコピーします。
     * コピー先にファイルが存在していた場合に上書きすることなく例外をスローします。
     * 
     * @param out コピー先ファイル
     * @throws IOException
     *          outが既に存在する、もしくはコピー時のIOに失敗した場合
     * @throws NullPointerException
     *          outがnullの場合
     * @since 2.0
     */
    public void copy(File out) throws IOException {
        copy(this, out);
    }
    
    /**
     * 入力ストリームを出力ストリームに出力します。
     * 
     * @param in 入力ストリーム
     * @param out 出力ストリーム
     * @throws IOException
     *          IOに失敗した場合
     * @throws NullPointerException
     *          inもしくはoutがnullの場合
     * @since 2.0
     */
    public static void copy(InputStream in, OutputStream out) throws IOException {
        if (in == null || out == null) {
            throw new NullPointerException("Stream-object is a null.");
        }
        int read;
        while ((read = in.read()) != -1) {
            out.write(read);
        }
        out.flush();
    }

    /**
     * ファイルをコピーします。
     * コピー先にファイルが存在していた場合に上書きすることなく例外をスローします。
     * 
     * @param in コピー元ファイル
     * @param out コピー先ファイル
     * @throws IOException
     *          outが既に存在する、もしくはコピー時のIOに失敗した場合
     * @throws FileNotFoundException
     *          inが存在しない場合
     * @throws NullPointerException
     *          inもしくはoutがnullの場合
     * @since 2.0
     */
    public static void copy(File in, File out) throws IOException {
        copy(in, out, false);
    }

    /**
     * ファイルをコピーします。
     * コピー先にファイルが存在していた場合に上書きするかどうかを指定することができます。
     * 
     * @param in コピー元ファイル
     * @param out コピー先ファイル
     * @param overwrite 既にファイルが存在している場合に上書きするならば true、例外をスローするならば false
     * @throws IOException
     *          outが既に存在する、もしくはコピー時のIOに失敗した場合
     * @throws FileNotFoundException
     *          inが存在しない場合
     * @throws NullPointerException
     *          inもしくはoutがnullの場合
     * @since 2.0
     */
    public static void copy(File in, File out, boolean overwrite) throws IOException {
        if (in == null || out == null) {
            throw new NullPointerException("File-object is a null.");
        }
        
        if (!in.exists()) {
            throw new FileNotFoundException("The input-file doesn't exist.");
        }        
        if (!overwrite) {
            if (out.exists()) {
                throw new IOException("The output-file exists already.");
            }
        }
        
        FileInputStream is = null;
        FileOutputStream os = null;
        try {
            is = new FileInputStream(in);
            os = new FileOutputStream(out);
            int read;
            while ((read = is.read()) != -1) {
                os.write(read);
            }
            os.flush();
        } finally {
            if (is != null) {
                try {
                    is.close();
                } finally {
                    if (os != null) {
                        os.close();
                    }
                }
            }
        }
    }
    

    private void constructor() throws IOException {
        if (exists() && isDirectory()) {
            throw new IOException("File is directory.");
        }
        if (checkFileName(getName()) == false) {
            throw new IOException("File-name is invalid.");
        }
    }
    
    /**
     * ファイル名から拡張子を抽出します。
     * なおディレクトリパスが含まれている場合は拡張子を正しく取得することはできません。
     * 
     * @param fileName ファイル名
     * @return 抽出した拡張子
     * @throws NullPointerException
     *          fileNameがnullの場合
     * @since 2.0
     */
    public static String getExtension(String fileName) throws NullPointerException {
        if (fileName == null) {
            throw new NullPointerException("file name is null.");
        }
        int pDot = fileName.lastIndexOf(DOT);
        if(pDot == -1){
            return "";
        }
        return fileName.substring(pDot + 1);
    }
    
}
