/**
 *
 * 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.activemq.karaf.commands;

import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.felix.gogo.commands.Action;
import org.apache.felix.gogo.commands.Argument;
import org.apache.felix.gogo.commands.basic.AbstractCommand;
import org.apache.felix.gogo.commands.basic.ActionPreparator;
import org.apache.felix.gogo.commands.basic.DefaultActionPreparator;
import org.apache.felix.service.command.CommandSession;
import org.apache.karaf.shell.console.BlueprintContainerAware;
import org.apache.karaf.shell.console.BundleContextAware;
import org.apache.karaf.shell.console.CompletableFunction;
import org.apache.karaf.shell.console.Completer;
import org.apache.karaf.shell.console.commands.GenericType;
import org.osgi.framework.BundleContext;
import org.osgi.service.blueprint.container.BlueprintContainer;
import org.osgi.service.blueprint.container.Converter;

/**
 * Base command to process options and wrap native ActiveMQ console commands.
 */
public class ActiveMQCommand extends AbstractCommand implements CompletableFunction
{
    protected BlueprintContainer blueprintContainer;
    protected Converter blueprintConverter;
    protected String actionId;
    protected List<Completer> completers;

    public void setBlueprintContainer(BlueprintContainer blueprintContainer) {
        this.blueprintContainer = blueprintContainer;
    }

    public void setBlueprintConverter(Converter blueprintConverter) {
        this.blueprintConverter = blueprintConverter;
    }

    public void setActionId(String actionId) {
        this.actionId = actionId;
    }

    @Override
    public List<Completer> getCompleters() {
        return completers;
    }

    public void setCompleters(List<Completer> completers) {
        this.completers = completers;
    }

    @Override
    protected ActionPreparator getPreparator() throws Exception {
        return new ActiveMQActionPreparator();
    }

    class ActiveMQActionPreparator extends DefaultActionPreparator {
        @Override
        public boolean prepare(Action action, CommandSession session, List<Object> params) throws Exception
        {
            Map<Argument, Field> arguments = new HashMap<Argument, Field>();
            List<Argument> orderedArguments = new ArrayList<Argument>();
            // Introspect
            for (Class type = action.getClass(); type != null; type = type.getSuperclass()) {
                for (Field field : type.getDeclaredFields()) {
                    Argument argument = field.getAnnotation(Argument.class);
                    if (argument != null) {
                        arguments.put(argument, field);
                        int index = argument.index();
                        while (orderedArguments.size() <= index) {
                            orderedArguments.add(null);
                        }
                        if (orderedArguments.get(index) != null) {
                            throw new IllegalArgumentException("Duplicate argument index: " + index);
                        }
                        orderedArguments.set(index, argument);
                    }
                }
            }
            // Check indexes are correct
            for (int i = 0; i < orderedArguments.size(); i++) {
                if (orderedArguments.get(i) == null) {
                    throw new IllegalArgumentException("Missing argument for index: " + i);
                }
            }
            // Populate
            Map<Argument, Object> argumentValues = new HashMap<Argument, Object>();
            int argIndex = 0;
            for (Iterator<Object> it = params.iterator(); it.hasNext();) {
                Object param = it.next();
                if (argIndex >= orderedArguments.size()) {
                    throw new IllegalArgumentException("Too many arguments specified");
                }
                Argument argument = orderedArguments.get(argIndex);
                if (!argument.multiValued()) {
                    argIndex++;
                }
                if (argument.multiValued()) {
                    List<Object> l = (List<Object>) argumentValues.get(argument);
                    if (l == null) {
                        l = new ArrayList<Object>();
                        argumentValues.put(argument, l);
                    }
                    l.add(param);
                } else {
                    argumentValues.put(argument, param);
                }
            }

            if (argumentValues.size() == 1 && arguments.size() == 1) {
                Object val = argumentValues.values().iterator().next();
                // short circut convert via blueprint... cause all our commands match this
                // bluepring was failing to convert the last long param to a string for browse
                // where dest is a numeric value - activemq-karaf-itests
                // see: org.apache.activemq.karaf.itest.ActiveMQBrokerFeatureTest.test()
                if (val instanceof List) {
                    Field field = arguments.values().iterator().next();
                    List<Object> values = (List<Object>) val;
                    ArrayList<String> convertedValues = new ArrayList<String>(values.size());
                    for (Object o : values) {
                        convertedValues.add(String.valueOf(o));
                    }
                    field.setAccessible(true);
                    field.set(action, convertedValues);
                    return true;
                }
            }

            for (Map.Entry<Argument, Object> entry : argumentValues.entrySet()) {
                Field field = arguments.get(entry.getKey());
                Object value = convert(action, session, entry.getValue(), field.getGenericType());
                field.setAccessible(true);
                field.set(action, value);
            }
            return true;
        }

        @Override
        protected Object convert(Action action, CommandSession commandSession, Object o, Type type) throws Exception {
            return blueprintConverter.convert(o, new GenericType(type));
        }
    }

    @Override
    public Action createNewAction() {
        Action action = (Action) blueprintContainer.getComponentInstance(actionId);
        if (action instanceof BlueprintContainerAware) {
            ((BlueprintContainerAware) action).setBlueprintContainer(blueprintContainer);
        }
        if (action instanceof BundleContextAware) {
            BundleContext context = (BundleContext) blueprintContainer.getComponentInstance("blueprintBundleContext");
            ((BundleContextAware) action).setBundleContext(context);
        }
        return action;
    }

    @Override
    public Map<String, Completer> getOptionalCompleters() {
        //TODO implement completers
        return null;
    }
}
