/*
 * Symphony - A modern community (forum/SNS/blog) platform written in Java.
 * Copyright (C) 2012-2017,  b3log.org & hacpai.com
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.b3log.symphony.util;

import org.apache.commons.io.IOUtils;
import org.b3log.latke.Keys;
import org.b3log.latke.Latkes;
import org.b3log.latke.ioc.LatkeBeanManager;
import org.b3log.latke.ioc.Lifecycle;
import org.b3log.latke.logging.Level;
import org.b3log.latke.logging.Logger;
import org.b3log.latke.repository.jdbc.JdbcRepository;
import org.b3log.latke.service.LangPropsService;
import org.b3log.latke.service.LangPropsServiceImpl;
import org.b3log.latke.util.CollectionUtils;
import org.b3log.symphony.SymphonyServletListener;
import org.b3log.symphony.model.Common;
import org.b3log.symphony.model.Option;
import org.b3log.symphony.service.OptionQueryService;
import org.json.JSONObject;

import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Symphony utilities.
 *
 * @author <a href="http://88250.b3log.org">Liang Ding</a>
 * @version 1.7.1.9, Jul 8, 2017
 * @since 0.1.0
 */
public final class Symphonys {

    /**
     * Configurations.
     */
    public static final ResourceBundle CFG = ResourceBundle.getBundle("symphony");

    /**
     * HacPai bot User-Agent.
     */
    public static final String USER_AGENT_BOT = "Mozilla/5.0 (compatible; HacPai/1.1; +https://hacpai.com)";

    /**
     * Reserved tags.
     */
    public static final String[] RESERVED_TAGS;

    /**
     * White list - tags.
     */
    public static final String[] WHITE_LIST_TAGS;

    /**
     * Reserved user names.
     */
    public static final String[] RESERVED_USER_NAMES;

    /**
     * Thread pool.
     */
    public static final ExecutorService EXECUTOR_SERVICE = Executors.newFixedThreadPool(50);

    /**
     * Logger.
     */
    private static final Logger LOGGER = Logger.getLogger(Symphonys.class);

    static {
        // Loads reserved tags
        final String reservedTags = CFG.getString("reservedTags");
        final String[] tags = reservedTags.split(",");
        RESERVED_TAGS = new String[tags.length];

        for (int i = 0; i < tags.length; i++) {
            final String tag = tags[i];

            RESERVED_TAGS[i] = tag.trim();
        }

        // Loads white list tags
        final String whiteListTags = CFG.getString("whitelist.tags");
        final String[] wlTags = whiteListTags.split(",");
        WHITE_LIST_TAGS = new String[wlTags.length];

        for (int i = 0; i < wlTags.length; i++) {
            final String tag = wlTags[i];

            WHITE_LIST_TAGS[i] = tag.trim();
        }

        // Loads reserved usernames
        final String reservedUserNames = CFG.getString("reservedUserNames");
        final String[] userNames = reservedUserNames.split(",");
        RESERVED_USER_NAMES = new String[userNames.length];

        for (int i = 0; i < userNames.length; i++) {
            final String userName = userNames[i];

            RESERVED_USER_NAMES[i] = userName.trim();
        }
    }

    static {
        try {
            final SSLContext ctx = SSLContext.getInstance("TLS");
            ctx.init(new KeyManager[0], new TrustManager[]{new X509TrustManager() {
                @Override
                public void checkClientTrusted(final X509Certificate[] arg0, final String arg1) {
                }

                @Override
                public void checkServerTrusted(final X509Certificate[] arg0, final String arg1) {
                }

                @Override
                public X509Certificate[] getAcceptedIssuers() {
                    return null;
                }
            }}, new SecureRandom());
            SSLContext.setDefault(ctx);
        } catch (final Exception e) {
            // ignore
        }

        // Reports status to Rhythm, I hope that everyone will be able to join in the SymHub plan :p
        new Timer(true).schedule(new TimerTask() {
            @Override
            public void run() {
                final String symURL = Latkes.getServePath();
                if (Networks.isIPv4(symURL)) {
                    return;
                }

                HttpURLConnection httpConn = null;
                OutputStream outputStream = null;
                try {
                    final LatkeBeanManager beanManager = Lifecycle.getBeanManager();
                    final OptionQueryService optionQueryService = beanManager.getReference(OptionQueryService.class);

                    final JSONObject statistic = optionQueryService.getStatistic();
                    final int articleCount = statistic.optInt(Option.ID_C_STATISTIC_ARTICLE_COUNT);
                    if (articleCount < 66) {
                        return;
                    }

                    final LangPropsService langPropsService = beanManager.getReference(LangPropsServiceImpl.class);

                    httpConn = (HttpURLConnection) new URL("https://rhythm.b3log.org/sym").openConnection();
                    httpConn.setConnectTimeout(10000);
                    httpConn.setReadTimeout(10000);
                    httpConn.setDoOutput(true);
                    httpConn.setRequestMethod("POST");
                    httpConn.setRequestProperty(Common.USER_AGENT, USER_AGENT_BOT);

                    httpConn.connect();

                    outputStream = httpConn.getOutputStream();
                    final JSONObject sym = new JSONObject();
                    sym.put("symURL", symURL);
                    sym.put("symTitle", langPropsService.get("symphonyLabel", Latkes.getLocale()));

                    IOUtils.write(sym.toString(), outputStream, "UTF-8");
                    outputStream.flush();

                    httpConn.getResponseCode();
                } catch (final Exception e) {
                    // ignore
                } finally {
                    IOUtils.closeQuietly(outputStream);

                    if (null != httpConn) {
                        try {
                            httpConn.disconnect();
                        } catch (final Exception e) {
                            // ignore
                        }
                    }

                    JdbcRepository.dispose();
                }
            }
        }, 1000 * 60 * 60 * 2, 1000 * 60 * 60 * 12);
    }

    /**
     * Private default constructor.
     */
    private Symphonys() {
    }

    /**
     * Gets all symphonies.
     *
     * @return a list of symphonies
     */
    public static List<JSONObject> getSyms() {
        HttpURLConnection httpConn = null;
        InputStream inputStream = null;

        try {
            httpConn = (HttpURLConnection) new URL("https://rhythm.b3log.org/syms").openConnection();
            httpConn.setConnectTimeout(10000);
            httpConn.setReadTimeout(10000);
            httpConn.setRequestMethod("GET");
            httpConn.setRequestProperty(Common.USER_AGENT, "B3log Symphony/" + SymphonyServletListener.VERSION);

            httpConn.connect();

            inputStream = httpConn.getInputStream();
            final String data = IOUtils.toString(inputStream, "UTF-8");
            final JSONObject result = new JSONObject(data);
            if (!result.optBoolean(Keys.STATUS_CODE)) {
                return Collections.emptyList();
            }

            return CollectionUtils.jsonArrayToList(result.optJSONArray("syms"));
        } catch (final Exception e) {
            LOGGER.log(Level.ERROR, "Gets syms from Rhythm failed", e);

            return Collections.emptyList();
        } finally {
            IOUtils.closeQuietly(inputStream);

            if (null != httpConn) {
                try {
                    httpConn.disconnect();
                } catch (final Exception e) {
                    // ignore
                }
            }
        }
    }

    /**
     * Does Symphony runs on development environment?
     *
     * @return {@code true} if it runs on development environment, {@code false} otherwise
     */
    public static boolean runsOnDevEnv() {
        return Latkes.RuntimeMode.DEVELOPMENT == Latkes.getRuntimeMode();
    }

    /**
     * Gets a configuration string property with the specified key.
     *
     * @param key the specified key
     * @return string property value corresponding to the specified key, returns {@code null} if not found
     */
    public static String get(final String key) {
        return CFG.getString(key);
    }

    /**
     * Gets a configuration boolean property with the specified key.
     *
     * @param key the specified key
     * @return boolean property value corresponding to the specified key, returns {@code null} if not found
     */
    public static Boolean getBoolean(final String key) {
        final String stringValue = get(key);

        if (null == stringValue) {
            return null;
        }

        return Boolean.valueOf(stringValue);
    }

    /**
     * Gets a configuration float property with the specified key.
     *
     * @param key the specified key
     * @return float property value corresponding to the specified key, returns {@code null} if not found
     */
    public static Float getFloat(final String key) {
        final String stringValue = get(key);
        if (null == stringValue) {
            return null;
        }

        return Float.valueOf(stringValue);
    }

    /**
     * Gets a configuration integer property with the specified key.
     *
     * @param key the specified key
     * @return integer property value corresponding to the specified key, returns {@code null} if not found
     */
    public static Integer getInt(final String key) {
        final String stringValue = get(key);
        if (null == stringValue) {
            return null;
        }

        return Integer.valueOf(stringValue);
    }

    /**
     * Gets a configuration long property with the specified key.
     *
     * @param key the specified key
     * @return long property value corresponding to the specified key, returns {@code null} if not found
     */
    public static Long getLong(final String key) {
        final String stringValue = get(key);
        if (null == stringValue) {
            return null;
        }

        return Long.valueOf(stringValue);
    }
}
