/* BdfServer - Copyright (c) 2013-2025 Vincent Calame - Exemole
 * Logiciel libre donné sous triple licence :
 * 1) selon les termes de la CeCILL V2
 * 2) selon les termes de l’EUPL V.1.1
 * 3) selon les termes de la GNU GPLv3
 * Voir le fichier licences.txt
 */


package fr.exemole.bdfserver.tools;

import fr.exemole.bdfserver.api.BdfServer;
import fr.exemole.bdfserver.api.configuration.PathConfiguration;
import fr.exemole.bdfserver.tools.docstream.DocStreamFactory;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.net.URI;
import java.net.URISyntaxException;
import java.text.ParseException;
import java.util.HashMap;
import java.util.Map;
import javax.xml.transform.Source;
import javax.xml.transform.TransformerException;
import javax.xml.transform.URIResolver;
import javax.xml.transform.stream.StreamSource;
import net.mapeadores.opendocument.css.parse.CssSource;
import net.mapeadores.util.io.DocStream;
import net.mapeadores.util.io.ResourceStorages;
import net.mapeadores.util.text.RelativePath;


/**
 *
 * @author Vincent Calame
 */
public final class BdfURI {

    private final static URI ROOT_URI = URI.create("bdf://this/");

    private BdfURI() {
    }

    public static URI toXmlPackURI(URI uri) {
        if (!uri.isAbsolute()) {
            return null;
        }
        if (uri.isOpaque()) {
            return null;
        }
        String scheme = uri.getScheme();
        if (!scheme.equals("bdf")) {
            return null;
        }
        uri = uri.normalize();
        try {
            return new URI(uri.getScheme(), uri.getAuthority(), "/xml-pack" + uri.getPath(), uri.getQuery(), uri.getFragment());
        } catch (URISyntaxException use) {
            return null;
        }
    }

    public static URI toAbsoluteBdfURI(String relativePath) {
        try {
            URI uri = new URI(relativePath);
            if (uri.isOpaque()) {
                return null;
            }
            uri = ROOT_URI.resolve(uri);
            uri = uri.normalize();
            return uri;

        } catch (URISyntaxException use) {
            return null;
        }
    }

    public static URI toURI(String href, String base) {
        href = getOldCompatibility(href);
        try {
            URI uri = new URI(href);
            if (uri.isOpaque()) {
                return null;
            }
            if (uri.isAbsolute()) {
                return uri.normalize();
            }
            if (base == null) {
                return null;
            }
            URI baseURI = new URI(base);
            if (!baseURI.isAbsolute()) {
                return null;
            }
            uri = baseURI.resolve(uri);
            return uri.normalize();
        } catch (URISyntaxException use) {
            return null;
        }
    }

    public static URIResolver getURIResolver(BdfServer bdfServer, PathConfiguration pathConfiguration) {
        return getURIResolver(bdfServer, pathConfiguration, false);
    }

    public static URIResolver getURIResolver(BdfServer bdfServer, PathConfiguration pathConfiguration, boolean withCache) {
        return new BdfURIResolver(bdfServer, pathConfiguration, withCache);
    }

    public static CssSource resolveCssSource(BdfServer bdfServer, PathConfiguration pathConfiguration, String href, String base) {
        CssResolver cssResolver = new BdfServerCssResolver(bdfServer, pathConfiguration);
        return cssResolver.resolve(href, base);
    }

    public static CssSource resolveCssSource(ResourceStorages resourceStorages, RelativePath relativePath) {
        ListCssResolver cssResolver = new ListCssResolver(resourceStorages);
        return cssResolver.resolveFirst(relativePath);
    }

    private static String getOldCompatibility(String href) {
        if (href.startsWith("bdf://this/xslt/")) {
            String relativePath = href.substring("bdf://this/xslt/".length());
            if (relativePath.indexOf('/') == -1) {
                return "bdf://this/xslt/v1/" + relativePath;
            } else {
                return href;
            }
        }
        if (href.startsWith("_common_")) {
            return "bdf://this/xslt/v1/" + href;
        }
        if (href.startsWith("bdf:_common_")) {
            return "bdf://this/xslt/v1/" + href.substring(4);
        }
        if (href.startsWith("bdf:_xml_/")) {
            return "bdf://this/xml/" + href.substring("bdf:_xml_/".length());
        }
        if (href.startsWith("bdf:_xslt_/")) {
            return "bdf://this/xslt/v1/" + href.substring("bdf:_xslt_/".length());
        }
        if (href.startsWith("bdf:_etc_/")) {
            return "bdf://this/xslt/v1/" + href.substring("bdf:_etc_/".length());
        }
        if (href.startsWith("bdf:_inc_/")) {
            return "bdf://this/inc/" + href.substring("bdf:_inc_/".length());
        }
        if (href.startsWith("bdf:_")) {
            return "bdf://this/dyn/" + href.substring("bdf:_".length());
        }
        if (href.startsWith("_import_")) {
            return "bdf://this/xslt/v1/" + href;
        }
        if (href.startsWith("_libs_")) {
            return "bdf://this/dyn/" + href.substring(1);
        }
        if (href.startsWith("_libscol_")) {
            return "bdf://this/dyn/" + href.substring(1);
        }
        return href;
    }

    private static Source toSource(URI uri, String s) {
        return new StreamSource(new StringReader(s), uri.toString());
    }


    private static class BdfURIResolver implements URIResolver {

        private final BdfServer bdfServer;
        private final PathConfiguration pathConfiguration;
        private Map<URI, String> cacheMap;

        private BdfURIResolver(BdfServer bdfServer, PathConfiguration pathConfiguration, boolean withCache) {
            this.bdfServer = bdfServer;
            this.pathConfiguration = pathConfiguration;
            if (withCache) {
                cacheMap = new HashMap<URI, String>();
            }
        }

        @Override
        public Source resolve(String href, String base) throws TransformerException {
            URI uri = toURI(href, base);
            if (uri == null) {
                return null;
            }
            if (cacheMap != null) {
                String val = cacheMap.get(uri);
                if (val != null) {
                    return toSource(uri, val);
                }
            }
            DocStream docStream = DocStreamFactory.buildDocStream(bdfServer, pathConfiguration, uri);
            if (docStream != null) {
                String docStreamText = docStream.getContent();
                if (cacheMap != null) {
                    cacheMap.put(uri, docStreamText);
                }
                return toSource(uri, docStreamText);
            }
            return null;
        }

    }


    private static class DocStreamCssSource implements CssSource {

        private final DocStream docStream;
        private final String uri;
        private final String base;
        private final CssResolver resolver;

        private DocStreamCssSource(CssResolver resolver, DocStream docStream, String uri) {
            this.docStream = docStream;
            this.uri = uri;
            int idx = uri.lastIndexOf('/');
            this.base = uri.substring(0, idx + 1);
            this.resolver = resolver;
        }

        @Override
        public Reader getReader() throws IOException {
            return docStream.getReader();
        }

        @Override
        public CssSource getImportCssSource(String url) {
            return resolver.resolve(url, base);
        }

        @Override
        public String getCssSourceURI() {
            return uri;
        }

    }


    private static abstract class CssResolver {

        public abstract CssSource resolve(String href, String base);

    }


    private static class BdfServerCssResolver extends CssResolver {

        private final BdfServer bdfServer;
        private final PathConfiguration pathConfiguration;

        private BdfServerCssResolver(BdfServer bdfServer, PathConfiguration pathConfiguration) {
            this.bdfServer = bdfServer;
            this.pathConfiguration = pathConfiguration;
        }

        @Override
        public CssSource resolve(String url, String base) {
            URI uri = toURI(url, base);
            if (uri == null) {
                return null;
            }
            DocStream docStream = DocStreamFactory.buildDocStream(bdfServer, pathConfiguration, uri);
            if (docStream == null) {
                return null;
            }
            if (docStream.getCharset() == null) {
                return null;
            }
            return new DocStreamCssSource(this, docStream, uri.toString());
        }

    }


    private static class ListCssResolver extends CssResolver {

        private final ResourceStorages resourceStorages;

        private ListCssResolver(ResourceStorages resourceStorages) {
            this.resourceStorages = resourceStorages;
        }

        public CssSource resolveFirst(RelativePath relativePath) {
            DocStream docStream = resourceStorages.getResourceDocStream(relativePath);
            if (docStream == null) {
                return null;
            }
            if (docStream.getCharset() == null) {
                return null;
            }
            return new DocStreamCssSource(this, docStream, relativePath.getPath());
        }

        @Override
        public CssSource resolve(String url, String base) {
            URI uri = toURI(url, base);
            if (uri == null) {
                return null;
            }
            uri = uri.normalize();
            String path = uri.getPath();
            if ((path == null) || (path.length() == 1)) {
                return null;
            }
            path = path.substring(1);
            int idx = path.indexOf('/');
            if (idx == -1) {
                return null;
            }
            RelativePath relativePath;
            try {
                relativePath = RelativePath.parse(path);
            } catch (ParseException pe) {
                return null;
            }
            DocStream docStream = resourceStorages.getResourceDocStream(relativePath);
            if (docStream == null) {
                return null;
            }
            if (docStream.getCharset() == null) {
                return null;
            }
            return new DocStreamCssSource(this, docStream, uri.toString());
        }

    }

}
