/*
 * This file is part of JICI, licensed under the MIT License (MIT).
 *
 * Copyright (c) 2015-2015 Aleksi Sapon <http://sapon.ca/jici/>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package ca.sapon.jici.evaluator.value.type;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

import ca.sapon.jici.util.ReflectionUtil;
import ca.sapon.jici.util.StringUtil;

/**
 *
 */
public class ObjectUnionValueType extends ObjectValueType {
    private final ObjectValueType[] types;
    private final Set<ObjectValueType> lowestUpperBound;

    public ObjectUnionValueType(ObjectValueType... types) {
        super(Object.class);
        if (types.length <= 1) {
            throw new IllegalArgumentException("Expected more than one type");
        }
        final Set<Class<?>> classes = new HashSet<>(types.length);
        boolean allEqual = true;
        Class<?> previous = null;
        for (ObjectValueType type : types) {
            if (type instanceof ObjectUnionValueType) {
                classes.addAll(((ObjectUnionValueType) type).getTypeClasses());
                allEqual = false;
            } else {
                final Class<?> _class = type.getTypeClass();
                classes.add(_class);
                if (previous == null) {
                    previous = _class;
                } else {
                    allEqual &= previous == _class;
                }
            }
        }
        if (allEqual) {
            throw new IllegalArgumentException("Expected differing types in the union");
        }
        this.types = types;
        final Set<Class<?>> bounds = ReflectionUtil.getLowestUpperBound(classes);
        lowestUpperBound = new HashSet<>(bounds.size());
        for (Class<?> bound : bounds) {
            final ObjectValueType type = new ObjectValueType(bound);
            lowestUpperBound.add(type);
        }
    }

    public Set<Class<?>> getTypeClasses() {
        final Set<Class<?>> classes = new HashSet<>(types.length);
        for (ObjectValueType type : types) {
            if (type instanceof ObjectUnionValueType) {
                classes.addAll(((ObjectUnionValueType) type).getTypeClasses());
            } else {
                classes.add(type.getTypeClass());
            }
        }
        return classes;
    }

    @Override
    public Class<?> getTypeClass() {
        throw new IllegalArgumentException("No type class for object type union");
    }

    @Override
    public String getName() {
        return '(' + StringUtil.toString(lowestUpperBound, ", ") + ')';
    }

    @Override
    public boolean is(ValueType type) {
        return type instanceof ObjectUnionValueType && this.lowestUpperBound.equals(((ObjectUnionValueType) type).lowestUpperBound);
    }

    @Override
    public boolean isArray() {
        for (ObjectValueType bound : lowestUpperBound) {
            if (bound.isArray()) {
                return true;
            }
        }
        return false;
    }

    @Override
    public ValueType unbox() {
        return this;
    }

    @Override
    public boolean convertibleTo(ValueType to) {
        for (ObjectValueType bound : lowestUpperBound) {
            if (bound.convertibleTo(to)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public Constructor<?> getConstructor(ValueType[] arguments) {
        for (ObjectValueType bound : lowestUpperBound) {
            try {
                return bound.getConstructor(arguments);
            } catch (IllegalArgumentException ignored) {
            }
        }
        return failGetConstructor(arguments);
    }

    @Override
    public Constructor<?> getVarargConstructor(ValueType[] arguments) {
        for (ObjectValueType bound : lowestUpperBound) {
            try {
                return bound.getVarargConstructor(arguments);
            } catch (IllegalArgumentException ignored) {
            }
        }
        return failGetConstructor(arguments);
    }

    private Constructor<?> failGetConstructor(ValueType[] arguments) {
        throw new IllegalArgumentException("No constructor for signature: "
                + "(" + StringUtil.toString(Arrays.asList(arguments), ", ") + ") in " + getName());
    }

    @Override
    public Field getField(String name) {
        for (ObjectValueType bound : lowestUpperBound) {
            try {
                return bound.getField(name);
            } catch (IllegalArgumentException ignored) {
            }
        }
        throw new IllegalArgumentException("No field named " + name + " in " + getName());
    }

    @Override
    public Method getMethod(String name, ValueType[] arguments) {
        for (ObjectValueType bound : lowestUpperBound) {
            try {
                return bound.getMethod(name, arguments);
            } catch (IllegalArgumentException ignored) {
            }
        }
        return failGetMethod(name, arguments);
    }

    @Override
    public Method getVarargMethod(String name, ValueType[] arguments) {
        for (ObjectValueType bound : lowestUpperBound) {
            try {
                return bound.getVarargMethod(name, arguments);
            } catch (IllegalArgumentException ignored) {
            }
        }
        return failGetMethod(name, arguments);
    }

    private Method failGetMethod(String name, ValueType[] arguments) {
        throw new IllegalArgumentException("No method for signature: "
                + name + "(" + StringUtil.toString(Arrays.asList(arguments), ", ") + ") in " + getName());
    }
}

@@ -3639,14 +3639,8 @@
 turn
- '(' +
  Str
@@ -3683,14 +3683,8 @@
 , %22)
- + ')'
 ;%0A  

Change name format for ObjectUnionValueType