/*
 * blanco Framework
 * Copyright (C) 2004-2008 IGA Tosiki
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 */
package blanco.svnconf;

import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;

import blanco.commons.util.BlancoFileUtil;
import blanco.commons.util.BlancoStringUtil;
import blanco.svnconf.valueobject.BlancoSvnConfStructure;
import blanco.svnconf.valueobject.BlancoSvnConfUserStructure;

/**
 * 「Subversionリポジトリ定義書」Excel様式から svn.confファイルを生成。
 * 
 * このクラスは、中間XMLファイルから設定ファイルを自動生成する機能を担います。
 * 
 * @author IGA Tosiki
 */
public class BlancoSvnConfXml2SvnConfFile {
    public static final String CMDLINE_PREFIX = "svnconf: ";

    /**
     * 自動生成するソースファイルの文字エンコーディング。
     */
    private String fEncoding = null;

    public void setEncoding(final String argEncoding) {
        fEncoding = argEncoding;
    }

    /**
     * 中間XMLファイルから設定ファイルを自動生成します。
     * 
     * @param argMetaXmlSourceFile
     *            メタ情報が含まれているXMLファイル。
     * @param argDirectoryTarget
     *            設定ファイル生成先ディレクトリ。
     * @param argAcceptLowerCaseUserName
     *            小文字化されたユーザ名を受け付けるかどうか。
     * @return メタ情報の配列。
     * @throws IOException
     *             入出力例外が発生した場合。
     */
    public BlancoSvnConfStructure[] process(final File argMetaXmlSourceFile,
            final File argDirectoryTarget,
            final boolean argAcceptLowerCaseUserName) throws IOException {
        final BlancoSvnConfStructure[] structures = new BlancoSvnConfParser()
                .parse(argMetaXmlSourceFile);

        for (int index = 0; index < structures.length; index++) {
            // メタ情報の解析結果をもとにソースコード自動生成を実行します。
            structure2File(structures[index], argDirectoryTarget,
                    argAcceptLowerCaseUserName);
        }

        return structures;
    }

    /**
     * 指定のシートの記述内容を展開します。
     * 
     * @param argStructure
     *            タスクの構造。
     * @param argDirectoryTarget
     *            出力先ディレクトリ
     * @param argAcceptLowerCaseUserName
     *            小文字化されたユーザ名を受け付けるかどうか。
     */
    public void structure2File(final BlancoSvnConfStructure argStructure,
            final File argDirectoryTarget,
            final boolean argAcceptLowerCaseUserName) {
        final File dirBlancoSvnConf = new File(argDirectoryTarget
                .getAbsolutePath()
                + "/svn.conf");
        dirBlancoSvnConf.mkdirs();
        final File fileSvnConf = new File(dirBlancoSvnConf.getAbsolutePath()
                + "/" + argStructure.getName() + ".svnauthz");
        expandSvnConf(argStructure, fileSvnConf, argAcceptLowerCaseUserName);
    }

    /**
     * Subversion用の設定ファイルを生成します。
     * 
     * @param argStructure
     *            構造体情報。
     * @param argTarget
     *            出力先ファイル。
     * @param argAcceptLowerCaseUserName
     *            小文字化されたユーザ名を受け付けるかどうか。
     */
    private void expandSvnConf(final BlancoSvnConfStructure argStructure,
            final File argTarget, final boolean argAcceptLowerCaseUserName) {
        final ByteArrayOutputStream outStream = new ByteArrayOutputStream();
        try {
            BufferedWriter writer = null;
            if (BlancoStringUtil.null2Blank(fEncoding).length() == 0) {
                writer = new BufferedWriter(new OutputStreamWriter(outStream));
            } else {
                writer = new BufferedWriter(new OutputStreamWriter(outStream,
                        fEncoding));
            }

            writeHead(argStructure, writer, argAcceptLowerCaseUserName);
            writer.newLine();

            writeBody(argStructure, writer, argAcceptLowerCaseUserName);
            writer.newLine();

            if ("new".equals(argStructure.getType())) {
                writeUsageNewRepos(argStructure, writer);
                writer.newLine();
            } else if ("update".equals(argStructure.getType())) {
                writeUsageUpdateRepos(argStructure, writer);
                writer.newLine();
            } else if ("delete".equals(argStructure.getType())) {
                writeUsageDeleteRepos(argStructure, writer);
                writer.newLine();
            }

            writeEndOfFile(writer);

            writer.close();
            writer.close();
        } catch (IOException ex) {
        }

        try {
            switch (BlancoFileUtil.bytes2FileIfNecessary(outStream
                    .toByteArray(), argTarget)) {
            case 1:
                System.out.println(CMDLINE_PREFIX + "create: "
                        + argTarget.getAbsolutePath());
                break;
            case 2:
                System.out.println(CMDLINE_PREFIX + "update: "
                        + argTarget.getAbsolutePath());
                break;
            }
        } catch (IOException e) {
            // TODO 自動生成された catch ブロック
            e.printStackTrace();
        }
    }

    /**
     * 設定ファイルヘッド領域の書き出し。
     * 
     * @param argStructure
     *            構造体情報。
     * @param writer
     *            出力先ライター。
     * @param argAcceptLowerCaseUserName
     *            小文字化されたユーザ名を受け付けるかどうか。
     * @throws IOException
     *             入出力例外が発生した場合。
     */
    private void writeHead(final BlancoSvnConfStructure argStructure,
            final BufferedWriter writer,
            final boolean argAcceptLowerCaseUserName) throws IOException {
        final SimpleDateFormat sdFormat = new SimpleDateFormat("yyyy/MM/dd");

        writer
                .write("############################################################");
        writer.newLine();
        writer.write("# <" + argStructure.getBaseDirectory() + ".conf/"
                + argStructure.getName() + ".svnauthz" + ">");
        writer.newLine();
        writer.write("#");
        writer.newLine();
        writer.write("# このファイルは [" + argStructure.getName()
                + "] リポジトリのための Subversion認証ファイルです。");
        writer.newLine();
        writer.write("#");
        writer.newLine();

        writer.write("# リポジトリ定義ID: " + argStructure.getName());
        writer.newLine();
        if (argStructure.getDescription() != null) {
            writer.write("# 説明: " + argStructure.getDescription());
            writer.newLine();
        }
        writer.write("# ホスト名: " + argStructure.getHostname());
        writer.newLine();
        writer.write("# 基準ディレクトリ: " + argStructure.getBaseDirectory());
        writer.newLine();
        writer.write("# 管理者ID: " + argStructure.getAdministratorName());
        writer.newLine();
        if (argStructure.getValidityPeriodBegin() != null) {
            writer.write("# 有効期間・開始: "
                    + sdFormat.format(argStructure.getValidityPeriodBegin()));
            writer.newLine();
        }
        if (argStructure.getValidityPeriodEnd() != null) {
            writer.write("# 有効期間・終了: "
                    + sdFormat.format(argStructure.getValidityPeriodEnd()));
            writer.newLine();
        }
        if (argAcceptLowerCaseUserName) {
            writer
                    .write("# 小文字化された利用者IDも受付。※環境により小文字利用者IDがSubversion認証に渡される現象に対応するため。");
            writer.newLine();
        }
        writer
                .write("############################################################");
        writer.newLine();
    }

    /**
     * 設定ファイル本体領域の書き出し。
     * 
     * @param argStructure
     *            構造体情報。
     * @param writer
     *            出力先ライター。
     * @param argAcceptLowerCaseUserName
     *            小文字化されたユーザ名を受け付けるかどうか。
     * @throws IOException
     *             入出力例外が発生した場合。
     */
    private void writeBody(final BlancoSvnConfStructure argStructure,
            final BufferedWriter writer,
            final boolean argAcceptLowerCaseUserName) throws IOException {
        final List<BlancoSvnConfUserStructure> listCommitter = new ArrayList<BlancoSvnConfUserStructure>();
        final List<BlancoSvnConfUserStructure> listReader = new ArrayList<BlancoSvnConfUserStructure>();
        for (int index = 0; index < argStructure.getUserList().size(); index++) {
            final BlancoSvnConfUserStructure user = (BlancoSvnConfUserStructure) argStructure
                    .getUserList().get(index);
            if ("rw".equals(user.getAuth())) {
                listCommitter.add(user);
            }
            if ("r".equals(user.getAuth())) {
                listReader.add(user);
            }
        }

        writer.write("[groups]");
        writer.newLine();

        {
            writer.write("# ●管理者(rw)");
            writer.newLine();

            final StringBuffer strbuf = new StringBuffer();
            final BlancoSvnConfUserStructure user = new BlancoSvnConfUserStructure();
            user.setName(argStructure.getAdministratorName());
            addUserLine(user, strbuf, writer, argAcceptLowerCaseUserName);
            writer.write("administrator = ");
            writer.write(strbuf.toString());
            writer.newLine();
            writer.newLine();
        }

        {
            writer.write("# ●読み書き(rw): " + listCommitter.size() + "名");
            writer.newLine();
            final StringBuffer strbuf = new StringBuffer();
            for (int index = 0; index < listCommitter.size(); index++) {
                final BlancoSvnConfUserStructure user = (BlancoSvnConfUserStructure) listCommitter
                        .get(index);
                if (index != 0) {
                    strbuf.append(",");
                }
                addUserLine(user, strbuf, writer, argAcceptLowerCaseUserName);
            }
            writer.write("committer = " + strbuf.toString());
        }
        writer.newLine();
        writer.newLine();

        {
            writer.write("# ●読み込み(r) : " + listReader.size() + "名");
            writer.newLine();
            final StringBuffer strbuf = new StringBuffer();
            for (int index = 0; index < listReader.size(); index++) {
                final BlancoSvnConfUserStructure user = (BlancoSvnConfUserStructure) listReader
                        .get(index);
                if (index != 0) {
                    strbuf.append(",");
                }
                addUserLine(user, strbuf, writer, argAcceptLowerCaseUserName);
            }
            writer.write("reader = " + strbuf.toString());
        }
        writer.newLine();
        writer.newLine();

        writer.write("[/]");
        writer.newLine();

        if ("r".equals(argStructure.getAnonymousAuth())
                || "rw".equals(argStructure.getAnonymousAuth())) {
            writer.write("# 非登録者利用あり");
            writer.newLine();
            writer.write("* = " + argStructure.getAnonymousAuth());
            writer.newLine();
            writer.newLine();
        } else {
            writer.write("# 非登録者利用は、ありません。");
            writer.newLine();
            writer.newLine();
        }

        writer.write("@administrator = rw");
        writer.newLine();
        writer.write("@committer = rw");
        writer.newLine();
        writer.write("@reader = r");
        writer.newLine();
    }

    /**
     * ユーザをあらわす行を処理
     * 
     * @param user
     * @param strbuf
     * @param writer
     * @param argAcceptLowerCaseUserName
     * @throws IOException
     */
    private void addUserLine(final BlancoSvnConfUserStructure user,
            final StringBuffer strbuf, final BufferedWriter writer,
            final boolean argAcceptLowerCaseUserName) throws IOException {
        strbuf.append(user.getName());
        boolean isLowerCaseUserNameNeed = false;
        if (argAcceptLowerCaseUserName) {
            // 小文字ユーザIDでもアクセスを許可する場合の処理。
            if (user.getName().toLowerCase().equals(user.getName()) == false) {
                isLowerCaseUserNameNeed = true;
            }
        }
        if (isLowerCaseUserNameNeed) {
            strbuf.append("," + user.getName().toLowerCase());
        }

        writer.write("# "
                + user.getName()
                + (isLowerCaseUserNameNeed ? (" ("
                        + user.getName().toLowerCase() + ")") : ""));
        if (BlancoStringUtil.null2Blank(user.getDescription()).length() > 0) {
            writer.write(" : "
                    + BlancoStringUtil.null2Blank(user.getDescription()));
        }
        writer.newLine();
    }

    private void writeUsageNewRepos(final BlancoSvnConfStructure argStructure,
            final BufferedWriter writer) throws IOException {
        writer.write("########################################");
        writer.newLine();
        writer.write("# ●Subversionリポジトリ作成コマンド例");
        writer.newLine();
        writer.write("# svnadmin create " + argStructure.getBaseDirectory()
                + "/" + argStructure.getName());
        writer.newLine();
        writer.newLine();
        writer.write("# ●Subversionリポジトリ初期ディレクトリ作成例");
        writer.newLine();
        writer.write("# # 空のディレクトリで実行してください。");
        writer.newLine();
        writer.write("# mkdir " + argStructure.getName() + ".wrk");
        writer.newLine();
        writer.write("# cd " + argStructure.getName() + ".wrk");
        writer.newLine();
        writer.write("# mkdir trunk");
        writer.newLine();
        writer.write("# mkdir branches");
        writer.newLine();
        writer.write("# mkdir tags");
        writer.newLine();
        writer.write("# svn import http://" + argStructure.getHostname()
                + "/svn/" + argStructure.getName() + " -m \"initial dir\"");
        writer.newLine();
        writer.write("# cd ..");
        writer.newLine();
        writer.write("########################################");
        writer.newLine();
    }

    private void writeUsageUpdateRepos(
            final BlancoSvnConfStructure argStructure,
            final BufferedWriter writer) throws IOException {
        writer.write("########################################");
        writer.newLine();
        writer.write("# ●Subversionリポジトリ更新");
        writer.newLine();
        writer.write("########################################");
        writer.newLine();
    }

    private void writeUsageDeleteRepos(
            final BlancoSvnConfStructure argStructure,
            final BufferedWriter writer) throws IOException {
        writer.write("########################################");
        writer.newLine();
        writer.write("# ●Subversionリポジトリ削除");
        writer.newLine();
        writer.write("# ディレクトリ削除予定 [" + argStructure.getBaseDirectory() + "/"
                + argStructure.getName() + "]");
        writer.newLine();
        writer.write("########################################");
        writer.newLine();
    }

    /**
     * end of file 領域の書き出し。
     * 
     * @param writer
     *            出力先ライター。
     * @throws IOException
     *             入出力例外が発生した場合。
     */
    private void writeEndOfFile(final BufferedWriter writer) throws IOException {
        writer.write("####################");
        writer.newLine();
        writer.write("# end of file");
        writer.newLine();
        writer.write("####################");
        writer.newLine();
    }
}
