/**
 * This file is part of the Meeds project (https://meeds.io/).
 *
 * Copyright (C) 2020 - 2025 Meeds Association contact@meeds.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */
package org.exoplatform.web.controller.router;

import java.io.IOException;
import java.util.BitSet;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;

import org.exoplatform.web.controller.QualifiedName;
import org.exoplatform.web.controller.metadata.ControllerDescriptor;
import org.exoplatform.web.controller.metadata.RouteDescriptor;
import org.exoplatform.web.url.MimeType;
import org.gatein.common.io.UndeclaredIOException;
import org.gatein.common.util.Tools;

/**
 * The router takes care of mapping a request to a a map.
 *
 */
public class Router {

    /** . */
    private static final BitSet escapeSet;

    static {
        // A subset of the path literals
        BitSet bs = new BitSet();
        bs.set('_');
        bs.set('.');
        bs.set('-');
        bs.set('~');
        bs.set('!');
        bs.set('$');
        bs.set('&');
        bs.set('+');
        bs.set(':');
        bs.set('@');

        //
        escapeSet = bs;
    }

    /** . */
    private final RegexFactory regexFactory;

    /** The root route. */
    final Route root;

    /** The slash escape char. */
    final char separatorEscape;

    /** . */
    final char separatorEscapeNible1;

    /** . */
    final char separatorEscapeNible2;

    /** . */
    private Regex[] regexes;

    public Router(ControllerDescriptor metaData) throws RouterConfigException {
        this(metaData, RegexFactory.JAVA);
    }

    public Router(ControllerDescriptor metaData, RegexFactory regexFactory) throws RouterConfigException {
        char separtorEscape = metaData.getSeparatorEscape();

        //
        int i = separtorEscape & ~0x7F;
        if (i > 0 || !escapeSet.get(separtorEscape)) {
            throw new RouterConfigException("Char " + (int) separtorEscape + " cannot be used a separator escape");
        }

        //
        String s = Integer.toString(separtorEscape, 16).toUpperCase();
        separatorEscapeNible1 = s.charAt(0);
        separatorEscapeNible2 = s.charAt(1);

        //
        this.regexFactory = regexFactory;
        this.root = new Route(this);
        this.separatorEscape = separtorEscape;
        this.regexes = new Regex[0];

        //
        for (RouteDescriptor routeMetaData : metaData.getRoutes()) {
            root.append(routeMetaData);
        }
    }

    Regex compile(String pattern) {
        for (Regex regex : regexes) {
            if (regex.getPattern().equals(pattern)) {
                return regex;
            }
        }
        Regex regex = regexFactory.compile(pattern);
        regex.index = regexes.length;
        regexes = Tools.appendTo(regexes, regex);
        return regex;
    }

    public void render(Map<QualifiedName, String> parameters, URIWriter writer) throws IOException {
        render(new RenderContext(parameters), writer);
    }

    public String render(Map<QualifiedName, String> parameters) {
        return render(new RenderContext(parameters));
    }

    public void render(RenderContext context, URIWriter writer) throws IOException {
        if (context.matchers == null) {
            context.matchers = new Regex.Matcher[regexes.length];
        }
        root.render(context, writer);
    }

    public String render(RenderContext context) {
        try {
            StringBuilder sb = new StringBuilder();
            URIWriter renderContext = new URIWriter(sb, MimeType.PLAIN);
            render(context, renderContext);
            return sb.toString();
        } catch (IOException e) {
            throw new UndeclaredIOException(e);
        }
    }

    public Map<QualifiedName, String> route(String path) {
        return route(path, Collections.<String, String[]> emptyMap());
    }

    public Map<QualifiedName, String> route(String path, Map<String, String[]> queryParams) {
        Iterator<Map<QualifiedName, String>> matcher = matcher(path, queryParams);
        if (matcher.hasNext()) {
            return matcher.next();
        } else {
            return null;
        }
    }

    public Iterator<Map<QualifiedName, String>> matcher(String path, Map<String, String[]> queryParams) {
        return root.route(path, queryParams);
    }

    @Override
    public String toString() {
        return "Router[" + root.toString() + "]";
    }
}
