/*******************************************************************************
 * Copyright (c) 2008 IGA Tosiki, NTT DATA BUSINESS BRAINS Corp.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    IGA Tosiki (NTT DATA BUSINESS BRAINS Corp.) - initial API and implementation
 *******************************************************************************/
/*
 * blanco Framework
 * Copyright (C) 2008 NTT DATA BUSINESS BRAINS CORPORATION
 * 
 * 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.properties;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Iterator;
import java.util.Locale;
import java.util.Properties;

import blanco.commons.util.BlancoStreamUtil;
import blanco.commons.util.BlancoStringUtil;
import blanco.properties.manifest.valueobject.BlancoPropertiesManifest;
import blanco.properties.resourcebundle.BlancoPropertiesResourceBundle;

/**
 * blancoPropertiesで内部的に利用するユーティリティクラス。
 * 
 * @author IGA Tosiki
 */
public class BlancoPropertiesUtil {
    private static final BlancoPropertiesResourceBundle fBundle = new BlancoPropertiesResourceBundle();

    /**
     * プラグイン名に .nl_<ロケール> と バージョン番号を付与した名称を取得します。
     * 
     * @param manifest
     *            マニフェスト。
     * @return サフィックス付きプラグイン名。
     */
    public static String getPluginNameWithSuffix(
            final BlancoPropertiesManifest manifest, final String targetLocale) {
        return manifest.getBundleSymbolicName() + "."
                + fBundle.getSymbolicNameSuffixWithoutLocale() + targetLocale
                + "_" + manifest.getBundleVersion();
    }

    /**
     * プラグインのフル名称からシンボリック名を類推します。
     * 
     * @param pluginFullName
     *            プラグインのフル名称。
     * @return シンボリック名。
     */
    public static String getBundleSymbolicName(final String pluginFullName) {
        // ポイント。アンダーバーで分割。
        final int underLine = pluginFullName.indexOf('_');
        if (underLine < 0) {
            throw new IllegalArgumentException("処理不能: getBundleSymbolicName("
                    + pluginFullName + ")");
        }
        return pluginFullName.substring(0, underLine);
    }

    /**
     * プラグインのフル名称からバージョンを類推します。
     * 
     * @param pluginFullName
     *            プラグインのフル名称。
     * @return バージョン。
     */
    public static String getBundleVersion(final String pluginFullName) {
        // ポイント。アンダーバーで分割。
        final int underLine = pluginFullName.indexOf('_');
        if (underLine < 0) {
            throw new IllegalArgumentException("処理不能: getBundleVersion("
                    + pluginFullName + ")");
        }
        return pluginFullName.substring(underLine + 1);
    }

    /**
     * 入力ストリームを全て読み込んでバイト配列化します。
     * 
     * @param inStream
     *            入力ストリーム。
     * @return バイト配列化された全データ。
     * @throws IOException
     *             入出力例外が発生した場合。
     */
    public static byte[] readBytes(final InputStream inStream)
            throws IOException {
        final ByteArrayOutputStream outStream = new ByteArrayOutputStream();
        BlancoStreamUtil.copy(inStream, outStream);
        outStream.flush();
        outStream.close();
        return outStream.toByteArray();
    }

    /**
     * 利用可能なロケール文字列の一覧を取得します。
     * 
     * @return 利用可能なロケール文字列の一覧。
     */
    public static String[] getAvailableLocaleStrings() {
        final Locale[] knownLocales = Locale.getAvailableLocales();
        final String[] wrkStrings = new String[knownLocales.length];
        for (int index = 0; index < knownLocales.length; index++) {
            wrkStrings[index] = knownLocales[index].toString();
        }
        return wrkStrings;
    }

    /**
     * 与えられたキー＋値を表すプロパティファイルの１行を文字列として取得します。
     * 
     * @param key
     *            キー。
     * @param value
     *            値。
     * @return プロパティファイル上に展開された際の１行。
     */
    public static String getPropertiesLine(final String key, final String value) {
        String result = null;
        final Properties prop = new Properties();
        final ByteArrayOutputStream outStream = new ByteArrayOutputStream();
        try {
            prop.setProperty(key, value);

            prop.store(outStream, "");
            outStream.flush();
            outStream.close();

            final byte[] buf = outStream.toByteArray();
            final BufferedReader reader = new BufferedReader(
                    new InputStreamReader(new ByteArrayInputStream(buf),
                            "UTF-8"));
            for (;;) {
                final String line = reader.readLine();
                if (line == null) {
                    break;
                }
                if (line.startsWith("#")) {
                    continue;
                }
                if (line.trim().length() == 0) {
                    continue;
                }
                result = line;
            }

            reader.close();

            if (result == null) {
                throw new IllegalArgumentException("ありえません");
            }
            return result;
        } catch (IOException e) {
            // これはありえない。
            throw new IllegalArgumentException("ありえない例外: ", e);
        }
    }

    /**
     * エスケープ文字が含まれたキー文字列から、エスケープが除去されたキー文字列を取得します。
     * 
     * 旧称: getKeyWithEscape
     * 
     * @param keyStringWithEscape
     *            エスケープが含まれたキー文字列。
     * @return エスケープが除去されたキー文字列。
     * @throws IOException
     */
    public static String normalizeKeyString(final String keyStringWithEscape) {
        final Properties prop = new Properties();
        try {
            prop.load(new ByteArrayInputStream(new String(keyStringWithEscape
                    + "=dummy").getBytes("UTF-8")));
        } catch (UnsupportedEncodingException e) {
            throw new IllegalArgumentException("ありえない例外が発生。", e);
        } catch (IOException e) {
            throw new IllegalArgumentException("ありえない例外が発生。", e);
        }

        final Iterator<Object> ite = prop.keySet().iterator();
        if (ite.hasNext() == false) {
            throw new IllegalArgumentException("ありえない例外が発生。");
        }
        return (String) ite.next();
    }

    // Babel URI に関するメソッド。

    /**
     * マニフェストから URIを生成します。
     * 
     * @param manifest
     *            マニフェスト。
     * @param strPropertyFilePath
     *            プロパティファイルのパス。(マニフェストから決定できない場合に利用する)
     * @return URI。
     * @throws IOException
     *             入出力例外が発生した場合。
     */
    public static String getBabelURI(BlancoPropertiesManifest manifest,
            String strPropertyFilePath) {
        // TODO バージョンを埋め込むなどの考察。
        // 例: babel://シンボル名:バージョン/プロパティファイルのパス

        // ￥が入っている場合には / に変換します。
        strPropertyFilePath = BlancoStringUtil.replaceAll(strPropertyFilePath,
                '\\', '/');

        return "babel://" + manifest.getBundleSymbolicName() + "/"
                + strPropertyFilePath;
    }

    public static String getHostFromBabelURI(final String bundleURI) {
        final String strUrl = "https://"
                + bundleURI.substring("https://".length());
        try {
            URL url = new URL(strUrl);
            return url.getHost();
        } catch (MalformedURLException e) {
            throw new IllegalArgumentException("URLが不正", e);
        }
    }

    public static String getPathFromBabelURI(final String bundleURI) {
        final String strUrl = "https://"
                + bundleURI.substring("https://".length());
        try {
            final URL url = new URL(strUrl);
            return url.getPath();
        } catch (MalformedURLException e) {
            throw new IllegalArgumentException("URLが不正", e);
        }
    }

    public static String getFileFromBabelURI(final String bundleURI) {
        final String strUrl = "https://"
                + bundleURI.substring("https://".length());
        try {
            final URL url = new URL(strUrl);
            return url.getFile();
        } catch (MalformedURLException e) {
            throw new IllegalArgumentException("URLが不正", e);
        }
    }

    /**
     * 与えられた URI が プリファランス・キーワードかどうかを判定します。
     * 
     * @param bundleURI
     * @return
     */
    public static boolean isPreferenceKeywords(final String bundleURI) {
        if (BlancoStringUtil.null2Blank(bundleURI).indexOf(
                ".properties?key=preferenceKeywords.") >= 0) {
            return true;
        }
        if (BlancoStringUtil.null2Blank(bundleURI).indexOf(
                ".properties?key=PreferenceKeywords.") >= 0) {
            return true;
        }

        if ("babel://org.eclipse.ltk.ui.refactoring/plugin.properties?key=RefactoringPropertyPage_keywords"
                .equals(bundleURI)) {
            return true;
        }
        if ("babel://org.eclipse.pde.api.tools.ui/plugin.properties?key=ApiProfilesPreferencePage.keywords"
                .equals(bundleURI)) {
            return true;
        }
        if ("babel://org.eclipse.pde.api.tools.ui/plugin.properties?key=ApiErrorsWarningsPreferencePage.keywords"
                .equals(bundleURI)) {
            return true;
        }
        if ("babel://org.eclipse.pde.api.tools.ui/plugin.properties?key=ApiErrorsWarningsPage.keywords"
                .equals(bundleURI)) {
            return true;
        }
        if ("babel://org.eclipse.pde.api.tools.ui/plugin.properties?key=ApiFiltersPage.keywords"
                .equals(bundleURI)) {
            return true;
        }

        // Spring IDE
        if ("babel://org.springframework.ide.eclipse.beans.ui/plugin.properties?key=propertyKeywords.label"
                .equals(bundleURI)) {
            return true;
        }

        return false;
    }
}
