package net.argius.filetool;

import java.io.*;

/**
 * ファイルの分割処理。
 * スレッドセーフではない。
 */
public class Split {

    private final long size;
    private final String prefix;
    private final int suffixLength;
    private final boolean numericSuffix;

    private boolean verbose;

    /**
     * Splitの生成。
     * @param size 分割サイズ
     * @param prefix ファイルのプレフィクス
     * @param suffixLength サフィックスの長さ
     * @param numericSuffix 数字のサフィックスを使用する場合は <code>true</code>
     */
    public Split(long size, String prefix, int suffixLength, boolean numericSuffix) {
        if (size < 1) {
            throw new IllegalArgumentException("size: " + size);
        }
        if (prefix == null) {
            throw new IllegalArgumentException("prefix: null");
        }
        this.size = size;
        this.prefix = prefix;
        this.suffixLength = suffixLength;
        this.numericSuffix = numericSuffix;
        this.verbose = false;
    }

    /**
     * verboseの取得。
     * @return verbose
     */
    public final boolean isVerbose() {
        return verbose;
    }

    /**
     * verboseの設定。
     * @param verbose verbose
     */
    public final void setVerbose(boolean verbose) {
        this.verbose = verbose;
    }

    /**
     * 情報の表示。
     * @param object 情報
     */
    protected void printInfo(Object object) {
        System.out.println(object);
    }

    /**
     * ファイル名の取得。
     * @param count カウント
     * @return ファイル名
     * @throws IOException 入出力エラーが発生した場合
     */
    protected String getFileName(int count) throws IOException {
        StringBuffer buffer = new StringBuffer();
        buffer.append(prefix);
        if (numericSuffix) {
            for (int i = 0, n = suffixLength - String.valueOf(count).length(); i < n; i++) {
                buffer.append('0');
            }
            buffer.append(count);
        } else {
            StringBuffer a = new StringBuffer();
            a.append(Integer.toString(count, 26));
            for (int i = 0, n = a.length(); i < n; i++) {
                char c = a.charAt(i);
                if ('0' <= c && c <= '9') {
                    c += 49;
                } else if ('a' <= c && c <= 'z') {
                    c += 10;
                }
                a.setCharAt(i, c);
            }
            if (a.length() > suffixLength) {
                throw new IOException("Output file suffixes exhausted");
            }
            for (int i = 0, n = suffixLength - a.length(); i < n; i++) {
                a.insert(0, 'a');
            }
            buffer.append(a);
        }
        return buffer.toString();
    }

    /**
     * 分割を実行する。
     * @param is 入力ストリーム
     * @throws IOException 入出力エラーが発生した場合
     */
    public final void execute(InputStream is) throws IOException {
        int counter = 0;
        while (true) {
            File file = File.createTempFile("split", ".tmp");
            file.deleteOnExit();
            long readLength;
            OutputStream os = new FileOutputStream(file);
            try {
                readLength = IOUtilities.transfer(is, os, size);
            } finally {
                os.close();
            }
            if (readLength > 0) {
                String name = getFileName(counter);
                if (verbose) {
                    printInfo("file=" + name + ", " + readLength + " bytes");
                }
                File newfile = new File(name);
                if (newfile.exists()) {
                    newfile.delete();
                }
                file.renameTo(newfile);
                ++counter;
            }
            if (readLength < size) {
                break;
            }
        }
    }

    /**
     * 処理の起動。
     * @param args パラメータ
     * @throws IOException 入出力エラーが発生した場合
     */
    private static void invoke(String[] args) throws IOException {
        long size = 1024L;
        String prefix = "x";
        int suffixLength = 2;
        boolean numericSuffix = false;
        boolean verbose = false;
        String input = "";
        int count = 0;
        for (int i = 0; i < args.length; i++) {
            String arg = args[i];
            if (arg.startsWith("-") && arg.length() > 1) {
                if (arg.equals("-a") || arg.equals("--suffix-length")) {
                    ++i;
                    if (args.length <= i) {
                        throw new IllegalArgumentException("option '"
                                                           + arg
                                                           + "' require 1 parameter");
                    }
                    boolean isInvalid = false;
                    try {
                        suffixLength = Integer.parseInt(args[i]);
                    } catch (NumberFormatException ex) {
                        isInvalid = true;
                    }
                    if (suffixLength < 1) {
                        isInvalid = true;
                    }
                    if (isInvalid) {
                        throw new IllegalArgumentException("invalid option: " + arg + ' ' + args[i]);
                    }
                } else if (arg.equals("-b") || arg.equals("--bytes")) {
                    ++i;
                    if (args.length <= i) {
                        throw new IllegalArgumentException("option '"
                                                           + arg
                                                           + "' require 1 parameter");
                    }
                    size = FileSize.toLong(args[i]);
                } else if (arg.equals("-d") || arg.equals("--numeric-suffixes")) {
                    numericSuffix = true;
                } else if (arg.equals("--verbose")) {
                    verbose = true;
                } else {
                    throw new IllegalArgumentException("invalid option: " + arg);
                }
            } else {
                ++count;
                if (count == 1) {
                    input = arg;
                } else if (count == 2) {
                    prefix = arg;
                }
            }
        }
        if (count < 1) {
            throw new IllegalArgumentException("requre input file");
        }
        if (count > 2) {
            throw new IllegalArgumentException("too much arguments: ");
        }
        // TODO 行分割モード
        Split split = new Split(size, prefix, suffixLength, numericSuffix);
        split.setVerbose(verbose);
        if (input.equals("-")) {
            split.execute(System.in);
        } else {
            File file = new File(input);
            if (!file.exists() || !file.isFile()) {
                throw new FileNotFoundException(input);
            }
            InputStream is = new FileInputStream(file);
            try {
                split.execute(is);
            } finally {
                is.close();
            }
        }
    }

    /**
     * 使い方の表示。
     */
    private static void printUsage() {
        PrintStream err = System.err;
        err.println("Split (argius-filetool)");
        err.println("usage : (command) [options] input [prefix]");
        err.println("options: ");
        err.println("  -a, --suffix-length=N   set suffix length (2)");
        err.println("  -b, --bytes=SIZE        split by SIZE bytes");
        err.println("  -d, --numeric-suffixes  use numeric suffix instead of alphabetic");
        err.println("      --verbose           verbose message mode");
        err.println("  -h, --help              show (this) help");
    }

    /**
     * Splitの実行。
     * @param args パラメータ(see 'usage')
     */
    public static void main(String[] args) {
        if (args.length == 0) {
            printUsage();
            return;
        }
        for (int i = 0; i < args.length; i++) {
            String arg = args[i];
            if (arg.equals("-h") || arg.equals("--help")) {
                printUsage();
                return;
            }
        }
        final String commandNameInfo = "Split: ";
        int status;
        try {
            invoke(args);
            status = 0;
        } catch (IllegalArgumentException ex) {
            System.err.println(commandNameInfo + ex.getMessage());
            status = 1;
        } catch (IOException ex) {
            System.out.println(commandNameInfo + ex.getMessage());
            status = 1;
        } catch (Exception ex) {
            String message = ex + Utilities.getFirstStackTrace(ex, Concatenation.class);
            System.out.println(commandNameInfo + message);
            status = 255;
        }
        if (status > 0) {
            System.exit(status);
        }
    }

}
