package act.cli;

/*-
 * #%L
 * ACT Framework
 * %%
 * Copyright (C) 2014 - 2017 ActFramework
 * %%
 * Licensed 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.
 * #L%
 */

import act.Act;
import act.app.App;
import act.app.AppServiceBase;
import act.cli.builtin.Exit;
import act.cli.builtin.Help;
import act.cli.builtin.IterateCursor;
import act.cli.meta.CommandMethodMetaInfo;
import act.cli.meta.CommanderClassMetaInfo;
import act.handler.CliHandler;
import act.handler.builtin.cli.CliHandlerProxy;
import org.osgl.Osgl;
import org.osgl.logging.LogManager;
import org.osgl.logging.Logger;
import org.osgl.util.C;
import org.osgl.util.E;
import org.osgl.util.S;

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

/**
 * Dispatch console command to CLI command handler
 */
public class CliDispatcher extends AppServiceBase<CliDispatcher> {

    private static Logger logger = LogManager.get(CliDispatcher.class);
    private static final String NAME_PART_SEPARATOR = "[\\.\\-_]+";

    private Map<String, CliHandler> registry = new HashMap<>();
    private Map<String, String> shortCuts = new HashMap<>();
    private Map<String, List<CliHandler>> ambiguousShortCuts = new HashMap<>();
    private Map<CliHandler, List<String>> nameMap = new HashMap<>();
    private Map<CliHandler, List<String>> shortCutMap = new HashMap<>();

    public CliDispatcher(App app) {
        super(app);
        registerBuiltInHandlers();
    }

    public CliDispatcher registerCommandHandler(String command, CommandMethodMetaInfo methodMetaInfo, CommanderClassMetaInfo classMetaInfo) {
        String sa[] = command.split(CommanderClassMetaInfo.NAME_SEPARATOR);
        for (String s : sa) {
            if (registry.containsKey(s)) {
                throw E.invalidConfiguration("Command %s already registered", command);
            }
            addToRegistry(s, new CliHandlerProxy(classMetaInfo, methodMetaInfo, app()));
            logger.debug("Command registered: %s", s);
        }
        return this;
    }

    public boolean registered(String command) {
        return registry.containsKey(command);
    }

    public CliHandler handler(String command) {
        String command0 = command;
        command = shortCuts.get(command);
        if (null == command) {
            command = command0;
        }
        CliHandler handler = registry.get(command);
        if (null == handler && !command.startsWith("act.")) {
            handler = registry.get("act." + command);
        }

        Act.Mode mode = Act.mode();
        if (null != handler && handler.appliedIn(mode)) {
            return handler;
        }

        return null;
    }

    /**
     * Returns a list of system commands in alphabetic order
     *
     * @return the system command list
     */
    public List<String> systemCommands() {
        return commands(true, false);
    }

    /**
     * Returns a list of application commands in alphabetic order
     *
     * @return the application command list
     */
    public List<String> applicationCommands() {
        return commands(false, true);
    }

    /**
     * Returns all commands in alphabetic order
     *
     * @return the list of commands
     */
    public List<String> commands(boolean sys, boolean app) {
        C.List<String> list = C.newList();
        Act.Mode mode = Act.mode();
        boolean all = !sys && !app;
        for (String s : registry.keySet()) {
            boolean isSysCmd = s.startsWith("act.");
            if (isSysCmd && !sys && !all) {
                continue;
            }
            if (!isSysCmd && !app && !all) {
                continue;
            }
            CliHandler h = registry.get(s);
            if (h.appliedIn(mode)) {
                list.add(s);
            }
        }
        return list.sorted(new Osgl.Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                boolean b1 = (o1.startsWith("act."));
                boolean b2 = (o2.startsWith("act."));
                if (b1 & !b2) {
                    return -1;
                }
                if (!b1 & b2) {
                    return 1;
                }
                return o1.compareTo(o2);
            }
        });
    }

    public List<String> names(CliHandler handler) {
        return nameMap.get(handler);
    }

    public List<String> shortCuts(CliHandler handler) {
        return shortCutMap.get(handler);
    }

    @Override
    protected void releaseResources() {
        registry.clear();
    }

    private void addToRegistry(String name, CliHandler handler) {
        registry.put(name, handler);
        Help.updateMaxWidth(name.length());
        updateNameIndex(name, handler);
        registerShortCut(name, handler);
    }

    private void updateNameIndex(String name, CliHandler handler) {
        List<String> nameList = nameMap.get(handler);
        if (null == nameList) {
            nameList = new ArrayList<>();
            nameMap.put(handler, nameList);
        }
        nameList.add(name);
    }

    private void registerShortCut(String name, CliHandler handler) {
        List<String> shortCutNames = shortCutMap.get(handler);
        if (null == shortCutNames) {
            shortCutNames = new ArrayList<>();
            shortCutMap.put(handler, shortCutNames);
        }
        for (int i = 0; i < 5; ++i) {
            String shortCut = shortCut(name, i);
            if (null == shortCut) {
                continue;
            }
            if (ambiguousShortCuts.containsKey(shortCut)) {
                List<CliHandler> list = ambiguousShortCuts.get(shortCut);
                list.add(handler);
            } else if (shortCuts.containsKey(shortCut)) {
                shortCuts.remove(shortCut);
                List<CliHandler> list = new ArrayList<>();
                ambiguousShortCuts.put(shortCut, list);
                for (List<String> ls : shortCutMap.values()) {
                    ls.remove(shortCut);
                }
            } else {
                shortCuts.put(shortCut, name);
                shortCutNames.add(shortCut);
            }
        }
    }

    /**
     * level:
     *
     * 0 - "foo.bar.zee" -> ".fbz"
     * 1 - "foo.bar.zee" -> "f.b.z"
     * 2 - "foo.bar.zee" -> "f.b.zee"
     * 3 - "foo.bar.zee" -> "f.bar.z"
     * 4 - "foo.bar.zee" -> "fo.ba.ze"
     */
    private static String shortCut(String name, int level) {
        String sa[] = name.split(NAME_PART_SEPARATOR);
        if (sa.length < 2) {
            return null;
        }
        S.Buffer sb = S.buffer();
        switch (level) {
            case 0:
                sb.append(".");
                for (String s : sa) {
                    sb.append(s.charAt(0));
                }
                return sb.toString();
            case 1:
                for (String s : sa) {
                    sb.append(s.charAt(0)).append(".");
                }
                sb.deleteCharAt(sb.length() - 1);
                return sb.toString();
            case 2:
                for (int i = 0; i < sa.length - 1; ++i) {
                    sb.append(sa[i].charAt(0)).append(".");
                }
                sb.append(sa[sa.length - 1]);
                return sb.toString();
            case 3:
                for (int i = 0; i < sa.length - 2; ++i) {
                    sb.append(sa[i].charAt(0)).append(".");
                }
                sb.append(sa[sa.length - 2]).append(".");
                sb.append(sa[sa.length - 1].charAt(0));
                return sb.toString();
            case 4:
                for (String s : sa) {
                    sb.append(s.charAt(0));
                    if (s.length() > 1) {
                        sb.append(s.charAt(1));
                    }
                    sb.append(".");
                }
                sb.deleteCharAt(sb.length() - 1);
                return sb.toString();
            default:
                throw E.unsupport();
        }
    }

    private void registerBuiltInHandlers() {
        addToRegistry("act.exit", Exit.INSTANCE);
        addToRegistry("act.quit", Exit.INSTANCE);
        addToRegistry("act.bye", Exit.INSTANCE);
        addToRegistry("act.help", Help.INSTANCE);
        addToRegistry("act.it", IterateCursor.INSTANCE);
    }
}
