/*
 *  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.codehaus.groovy.runtime;

import groovy.lang.Closure;
import groovy.util.ProxyGenerator;
import org.codehaus.groovy.runtime.typehandling.GroovyCastException;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Runtime support for intersection-type cast and {@code as} coercion
 * (GROOVY-11998). Compiler-generated bytecode for {@code (A & B) value} and
 * {@code value as (A & B)} routes through these helpers when the target is
 * an intersection type and the source is not a native lambda or method
 * reference (those cases are handled at compile time via
 * {@code LambdaMetafactory.altMetafactory} markers).
 *
 * @since 6.0.0
 */
public final class IntersectionCastSupport {

    private IntersectionCastSupport() {}

    /**
     * Strict cast: every component must already be assignment-compatible with
     * the source's runtime class. Throws {@link GroovyCastException} otherwise.
     */
    public static Object castTo(final Object source, final Class<?>[] components) {
        if (source == null) return null;
        for (Class<?> c : components) {
            if (!c.isInstance(source)) {
                throw new GroovyCastException(source, c);
            }
        }
        return source;
    }

    /**
     * Coercion: produce an object that satisfies every component. For
     * {@link Closure} and {@link Map} sources, a multi-interface proxy is
     * built via {@link ProxyGenerator}. For other sources, falls back to
     * {@link #castTo} so the behaviour is at least as strict as a cast.
     */
    public static Object asType(final Object source, final Class<?>[] components) {
        if (source == null) return null;

        if (isInstanceOfAll(source, components)) {
            return source;
        }

        Class<?> baseClass = pickBaseClass(components);
        @SuppressWarnings({"unchecked", "rawtypes"})
        List<Class> ifaces = (List) filterInterfaces(components);

        if (source instanceof Closure) {
            Map<String, Closure> closureMap = new HashMap<>();
            closureMap.put("*", (Closure<?>) source); // wildcard: all SAM calls go to the closure
            return ProxyGenerator.INSTANCE.instantiateAggregate(closureMap, ifaces, baseClass, null);
        }
        if (source instanceof Map) {
            return ProxyGenerator.INSTANCE.instantiateAggregate((Map) source, ifaces, baseClass, null);
        }

        // Strict fallback: throws GroovyCastException with a useful message
        return castTo(source, components);
    }

    private static boolean isInstanceOfAll(final Object source, final Class<?>[] components) {
        for (Class<?> c : components) if (!c.isInstance(source)) return false;
        return true;
    }

    private static Class<?> pickBaseClass(final Class<?>[] components) {
        for (Class<?> c : components) if (!c.isInterface()) return c;
        return null;
    }

    private static List<Class<?>> filterInterfaces(final Class<?>[] components) {
        List<Class<?>> out = new ArrayList<>(components.length);
        for (Class<?> c : components) if (c.isInterface()) out.add(c);
        return out;
    }
}
