/*
 * Copyright (C) 2012 Darth Affe <http://wyrez.org> and contributors
 *
 * 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.wyrez.persij;

import java.lang.reflect.Array;
import java.util.HashMap;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Mapping and object container for PersiJ
 *
 * @author Darth Affe
 */
public final class PersiJContainer {

    private static final Logger logger = Logger.getLogger("org.wyrez.persij");
    private final HashMap<Class, PersiJMapping> mappings = new HashMap<Class, PersiJMapping>();

    public PersiJContainer() {
        registerSingleton(PersiJContainer.class, this);
    }

    /**
     * Registers a mapping
     *
     * @param type Type which should be mapped
     */
    public final <T> void registerType(Class<T> type) {
        registerType(type, false);
    }

    /**
     * Registers a mapping
     *
     * @param type Type which should be mapped
     * @param singleton Singleton indicator
     */
    public final <T> void registerType(Class<T> type, boolean singleton) {
        registerType(type, type, singleton);
    }

    /**
     * Registers a mapping
     *
     * @param type Source-type which should be mapped
     * @param mapping Target-type which should be mapped
     */
    public final <T> void registerType(Class<T> type, Class<? extends T> mapping) {
        registerType(type, mapping, false);
    }

    /**
     * Registers a mapping
     *
     * @param type Source-type which should be mapped
     * @param mapping Target-type which should be mapped
     * @param singleton Singleton indicator
     */
    public final <T> void registerType(Class<T> type, Class<? extends T> mapping, boolean singleton) {
        mappings.put(type, new PersiJMapping(mapping, singleton));
    }

    /**
     * Registers a singleton-mapping to an existing object
     *
     * @param type Source-type which should be mapped
     * @param instance Singleton instance
     */
    public final <T> void registerSingleton(Class<T> type, T instance) {
        mappings.put(type, new PersiJMapping(type, instance));
    }

    /**
     * Tests if the specified type is registered
     *
     * @param type Type which should be tested
     * @return true if the type is registered; else otherwise
     */
    public final boolean isTypeRegistered(Class type) {
        return mappings.containsKey(type);
    }

    /**
     * Creates an instance of the type mapped to the given type
     *
     * @param <T> Type to resolve
     * @param type Class of the type to resolve
     * @return Insance of T
     */
    public final <T> T resolve(Class<T> type) {
        PersiJMapping mapping = mappings.get(type);
        if (mapping != null) {
            try {
                return (T) mapping.getInstance(this, null);
            } catch (NoSuitableConstructorFoundException ex) {
                logger.log(Level.WARNING, "Can't create object for type: "
                        + type.getName(), ex);
                return null;
            }
        } else {
            logger.log(Level.WARNING, "Can't find a mapping for type: "
                    + type.getName());
            return null;
        }
    }

    /**
     * Creates an instance of the type mapped to the given type
     *
     * @param <T> Type to resolve
     * @param type Class of the type to resolve
     * @param paramMapping
     * @return Instance of T
     */
    public final <T> T resolve(Class<T> type, PersiJParamMapping paramMapping) {
        PersiJMapping mapping = mappings.get(type);
        if (mapping != null) {
            try {
                return (T) mapping.getInstance(this, paramMapping);
            } catch (NoSuitableConstructorFoundException ex) {
                logger.log(Level.WARNING, "Can't create object for type: "
                        + type.getName(), ex);
                return null;
            }
        } else {
            logger.log(Level.WARNING, "Can't find a mapping for type: "
                    + type.getName());
            return null;
        }
    }

    /**
     * Creates an array with instances of the type mapped to the given type
     *
     * @param <T> Type to resolve
     * @param type Class of the type to resolve
     * @param number Size of the array
     * @return Array of T
     */
    public final <T> T[] resolve(Class<T> type, int number) {
        PersiJMapping mapping = mappings.get(type);
        if (mapping != null) {
            try {
                T[] array = (T[]) Array.newInstance(type, number);
                return (T[]) mapping.getInstances(this, null, array);
            } catch (NoSuitableConstructorFoundException ex) {
                logger.log(Level.WARNING, "Can't create object for type: "
                        + type.getName(), ex);
                return null;
            }
        } else {
            logger.log(Level.WARNING, "Can't find a mapping for type: "
                    + type.getName());
            return null;
        }
    }
}
