/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.ignite.console.agent;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsonorg.JsonOrgModule;
import io.socket.client.Ack;
import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.ProtectionDomain;
import java.util.Arrays;
import org.apache.log4j.Logger;
import org.json.JSONArray;
import org.json.JSONObject;

/**
 * Utility methods.
 */
public class AgentUtils {
    /** */
    private static final Logger log = Logger.getLogger(AgentUtils.class.getName());

    /** JSON object mapper. */
    private static final ObjectMapper MAPPER = new ObjectMapper();

    static {
        // Register special module with basic serializers.
        MAPPER.registerModule(new JsonOrgModule());
    }

    /** */
    private static final Ack NOOP_CB = new Ack() {
        @Override public void call(Object... args) {
            if (args != null && args.length > 0 && args[0] instanceof Throwable)
                log.error("Failed to execute request on agent.", (Throwable) args[0]);
            else
                log.info("Request on agent successfully executed " + Arrays.toString(args));
        }
    };

    /**
     * Default constructor.
     */
    private AgentUtils() {
        // No-op.
    }

    /**
     * @param path Path to normalize.
     * @return Normalized file path.
     */
    public static String normalizePath(String path) {
        return path != null ? path.replace('\\', '/') : null;
    }

    /**
     * @return App folder.
     */
    public static File getAgentHome() {
        try {
            ProtectionDomain domain = AgentLauncher.class.getProtectionDomain();

            // Should not happen, but to make sure our code is not broken.
            if (domain == null || domain.getCodeSource() == null || domain.getCodeSource().getLocation() == null) {
                log.warn("Failed to resolve agent jar location!");

                return null;
            }

            // Resolve path to class-file.
            URI classesUri = domain.getCodeSource().getLocation().toURI();

            boolean win = System.getProperty("os.name").toLowerCase().contains("win");

            // Overcome UNC path problem on Windows (http://www.tomergabel.com/JavaMishandlesUNCPathsOnWindows.aspx)
            if (win && classesUri.getAuthority() != null)
                classesUri = new URI(classesUri.toString().replace("file://", "file:/"));

            return new File(classesUri).getParentFile();
        }
        catch (URISyntaxException | SecurityException ignored) {
            log.warn("Failed to resolve agent jar location!");

            return null;
        }
    }

    /**
     * Gets file associated with path.
     * <p>
     * First check if path is relative to agent home.
     * If not, check if path is absolute.
     * If all checks fail, then {@code null} is returned.
     * <p>
     *
     * @param path Path to resolve.
     * @return Resolved path as file, or {@code null} if path cannot be resolved.
     */
    public static File resolvePath(String path) {
        assert path != null;

        File home = getAgentHome();

        if (home != null) {
            File file = new File(home, normalizePath(path));

            if (file.exists())
                return file;
        }

        // 2. Check given path as absolute.
        File file = new File(path);

        if (file.exists())
            return file;

        return null;
    }

    /**
     * Get callback from handler arguments.
     *
     * @param args Arguments.
     * @return Callback or noop callback.
     */
    public static Ack safeCallback(Object[] args) {
        boolean hasCb = args != null && args.length > 0 && args[args.length - 1] instanceof Ack;

        return hasCb ? (Ack)args[args.length - 1] : NOOP_CB;
    }

    /**
     * Remove callback from handler arguments.
     *
     * @param args Arguments.
     * @return Arguments without callback.
     */
    public static Object[] removeCallback(Object[] args) {
        boolean hasCb = args != null && args.length > 0 && args[args.length - 1] instanceof Ack;

        return hasCb ? Arrays.copyOf(args, args.length - 1) : args;
    }

    /**
     * Map java object to JSON object.
     *
     * @param obj Java object.
     * @return {@link JSONObject} or {@link JSONArray}.
     * @throws IllegalArgumentException If conversion fails due to incompatible type.
     */
    public static Object toJSON(Object obj) {
        if (obj instanceof Iterable)
            return MAPPER.convertValue(obj, JSONArray.class);

        return MAPPER.convertValue(obj, JSONObject.class);
    }

    /**
     * Map JSON object to java object.
     *
     * @param obj {@link JSONObject} or {@link JSONArray}.
     * @param toValType Expected value type.
     * @return Mapped object type of {@link T}.
     * @throws IllegalArgumentException If conversion fails due to incompatible type.
     */
    public static <T> T fromJSON(Object obj, Class<T> toValType) throws IllegalArgumentException {
        return MAPPER.convertValue(obj, toValType);
    }
}
