/*
 * Copyright (c) 2014 Magnet Systems, Inc.
 * All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you
 * may not use this file except in compliance with the License. You
 * may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */

package com.magnet.plugin.r2m.ui.chooser;

import com.intellij.openapi.ui.Messages;
import com.magnet.langpack.builder.rest.parser.ExampleParser;
import com.magnet.langpack.builder.rest.parser.RestExampleModel;
import com.magnet.plugin.r2m.helpers.IOUtils;
import com.magnet.plugin.common.Logger;
import com.magnet.plugin.r2m.helpers.UIHelper;
import com.magnet.plugin.common.helpers.URLHelper;
import com.magnet.plugin.r2m.messages.R2MMessages;
import com.magnet.plugin.r2m.models.ExampleResource;
import com.magnet.plugin.r2m.models.ExamplesManifest;
import sun.net.www.protocol.file.FileURLConnection;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.List;

/**
 * Helper for REST example chooser
 */
public class ExampleChooserHelper {

    private static final String MANIFEST_URL = ExampleResource.EXAMPLES_BASE_URL + "manifest.json";

    private static ExamplesManifest EXAMPLES_MANIFEST = null;

    private static volatile boolean EXAMPLES_DIALOG_UP = false;

    public static synchronized boolean isExamplesDialogUp() {
        return EXAMPLES_DIALOG_UP;
    }

    public static String showExamplesDialog() {

        EXAMPLES_DIALOG_UP = true;
        try {
            List<String> examples = getManifest().getExamplesList();
            String response = Messages.showEditableChooseDialog(
                    R2MMessages.getMessage("CHOOSE_EXAMPLE_LABEL"),
                    R2MMessages.getMessage("CHOOSE_EXAMPLE_TITLE"),
                    Messages.getQuestionIcon(),
                    examples.toArray(new String[examples.size()]),
                    R2MMessages.getMessage("CHOOSE_EXAMPLE_DEFAULT_VALUE"),
                    null);
            return response == null ? null : response.split(ExamplesManifest.DESCRIPTION_SEPARATOR_KEY)[0];
        } finally {
            EXAMPLES_DIALOG_UP = false;
        }
    }


    private static ExamplesManifest getManifest() {
        if (null != EXAMPLES_MANIFEST) {
            return EXAMPLES_MANIFEST;
        }
        InputStream infoStream = null;
        try {
            infoStream = URLHelper.loadUrl(MANIFEST_URL);
            String json = IOUtils.toString(infoStream);
            EXAMPLES_MANIFEST = new ExamplesManifest(json);
        } catch (Exception ex) {
            UIHelper.showErrorMessage("Couldn't parse manifest at URL: " + MANIFEST_URL);
            Logger.error(ExampleChooserHelper.class, ex.getMessage());
            EXAMPLES_MANIFEST = null;
        } finally {
            if (infoStream != null) {
                try {
                    infoStream.close();
                } catch (IOException e1) {
                    // ignore
                }
            }
        }
        return EXAMPLES_MANIFEST;
    }

    public static List<RestExampleModel> getControllersMethodsByName(String name) {
        String url = getExampleUrlByName(name);
        if (null == url) {
            return null;
        }
        return getControllersMethodsByUrl(url);
    }

    private static String getExampleUrlByName(String name) {
        ExamplesManifest manifest = getManifest();
        ExampleResource example = manifest.getExample(name);
        if (null == example) {
            return null;
        }
        return example.getUrl();

    }

    public static List<RestExampleModel> getControllersMethodsByUrl(String urlString) {
        ExampleParser parser = new ExampleParser();
        List<RestExampleModel> methodModels = new ArrayList<RestExampleModel>();
        try {
            if (!ping(urlString, 5000)) {
                UIHelper.showErrorMessage("URL not reachable: " + urlString);
                return null;
            }
            URL url = new URL(urlString);
            methodModels.addAll(parser.parseExample(url));
        } catch (MalformedURLException e) {
            return null;
        }
        return methodModels;
    }

    public static List<RestExampleModel> getControllersMethodsByFile(File file) {
        List<RestExampleModel> methodModels = new ArrayList<RestExampleModel>();
        try {
            if (file.isFile()) {
                return getControllersMethodsByUrl(file.toURI().toURL().toString());
            }
            if (file.isDirectory()) {
                File[] files = file.listFiles(new FileFilter() {
                    @Override
                    public boolean accept(File file) {
                        return file.isFile() && !file.getName().startsWith(".");
                    }
                });
                if (files != null && files.length > 0) {
                    for (File f: files) {
                        methodModels.addAll(getControllersMethodsByUrl(f.toURI().toURL().toString()));
                    }
                }

            }
        } catch (MalformedURLException e) {
            e.printStackTrace();
            // should not happen
        }
        return methodModels;
    }

    /**
     * Found on http://stackoverflow.com/questions/3584210/preferred-java-way-to-ping-a-http-url-for-availability
     * Pings a HTTP URL. This effectively sends a HEAD request and returns <code>true</code> if the response code is in
     * the 200-399 range.
     * @param url The HTTP URL to be pinged.
     * @param timeout The timeout in millis for both the connection timeout and the response read timeout. Note that
     * the total timeout is effectively two times the given timeout.
     * @return <code>true</code> if the given HTTP URL has returned response code 200-399 on a HEAD request within the
     * given timeout, otherwise <code>false</code>.
     */
    public static boolean ping(String url, int timeout) {
        try {
            URLConnection conn  = new URL(url).openConnection();
            if (conn instanceof FileURLConnection) {
                return true;
            }
            HttpURLConnection connection = (HttpURLConnection) conn;
            connection.setConnectTimeout(timeout);
            connection.setReadTimeout(timeout);
            connection.setRequestMethod("HEAD");
            int responseCode = connection.getResponseCode();
            return (200 <= responseCode && responseCode <= 399);
        } catch (IOException exception) {
            return false;
        }
    }

}