package org.yinwang.pysonar.demos;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.yinwang.pysonar.*;
import org.yinwang.pysonar.types.ModuleType;

import java.io.File;
import java.util.*;
import java.util.Map.Entry;
import java.util.regex.Pattern;

/**
 * Collects per-file hyperlinks, as well as styles that require the
 * symbol table to resolve properly.
 */
class Linker {

    private static final Pattern CONSTANT = Pattern.compile("[A-Z_][A-Z0-9_]*");

    // Map of file-path to semantic styles & links for that path.
    @NotNull
    private Map<String, List<StyleRun>> fileStyles = new HashMap<>();

    private File outDir;  // where we're generating the output html
    private String rootPath;

    /**
     * Constructor.
     * @param root the root of the directory tree being indexed
     * @param outdir the html output directory
     */
    public Linker(String root, File outdir) {
        rootPath = root;
        outDir = outdir;
    }

    /**
     * Process all bindings across all files and record per-file semantic styles.
     * Should be called once per index.
     */

    public void findLinks(@NotNull Indexer indexer) {
        Util.msg("Adding xref links");

        int ndef = 0;
        for (List<Binding> bindings : indexer.getAllBindings().values()) {
            for (Binding b : bindings) {
                ndef += b.getDefs().size();
            }
        }

        FancyProgress progress = new FancyProgress(ndef, 50);
        for (List<Binding> bindings : indexer.getAllBindings().values()) {
            for (Binding b : bindings) {
                addSemanticStyles(b);
                for (Def def : b.getDefs()) {
                    processDef(def, b);
                    progress.tick();
                }
            }
        }

        // highlight definitions
        Util.msg("\nAdding ref links");
        progress = new FancyProgress(indexer.getReferences().size(), 50);

        for (Entry<Ref,List<Binding>> e : indexer.getReferences().entrySet()) {
            processRef(e.getKey(), e.getValue());
            progress.tick();
        }


//        for (List<Diagnostic> ld: indexer.semanticErrors.values()) {
//            for (Diagnostic d: ld) {
//                processDiagnostic(d);
//            }
//        }

//        for (List<Diagnostic> ld: indexer.parseErrors.values()) {
//            for (Diagnostic d: ld) {
//                processDiagnostic(d);
//            }
//        }
    }

    private void processDef(@Nullable Def def, @NotNull Binding binding) {
        if (def == null || def.isURL() || def.getStart() < 0) {
            return;
        }
        StyleRun style = new StyleRun(StyleRun.Type.ANCHOR, def.getStart(), def.getLength());
        style.message = binding.getQname() + " : " + binding.getType();
        style.url = binding.getQname();
        style.id = "" + Math.abs(def.hashCode());

        Set<Ref> refs = binding.getRefs();
        style.highlight = new ArrayList<>();


        for (Ref r : refs) {
            style.highlight.add(Integer.toString(Math.abs(r.hashCode())));
        }
        addFileStyle(def.getFile(), style);
    }


    void processRef(@NotNull Ref ref, @NotNull List<Binding> bindings) {
        StyleRun link = new StyleRun(StyleRun.Type.LINK, ref.start(), ref.length());
        link.id = Integer.toString(Math.abs(ref.hashCode()));

        List<String> typings = new ArrayList<>();
        for (Binding b : bindings) {
            typings.add(b.getQname() + " : " + b.getType());
        }
        link.message = Util.joinWithSep(typings, " | ", "{", "}");

        link.highlight = new ArrayList<>();

        for (Binding b : bindings) {
            for (Def d : b.getDefs()) {
                link.highlight.add(Integer.toString(Math.abs(d.hashCode())));
            }
        }


        // Currently jump to the first binding only. Should change to have a
        // hover menu or something later.
        String path = ref.getFile();
        for (Binding b : bindings) {
            if (link.url == null) {
                link.url = toURL(b, path);
            }

            if (link.url != null) {
                addFileStyle(path, link);
                break;
            }
        }
    }


    /**
     * Returns the styles (links and extra styles) generated for a given file.
     * @param path an absolute source path
     * @return a possibly-empty list of styles for that path
     */
    public List<StyleRun> getStyles(String path) {
        return stylesForFile(path);
    }

    private List<StyleRun> stylesForFile(String path) {
        List<StyleRun> styles = fileStyles.get(path);
        if (styles == null) {
            styles = new ArrayList<StyleRun>();
            fileStyles.put(path, styles);
        }
        return styles;
    }

    private void addFileStyle(String path, StyleRun style) {
        stylesForFile(path).add(style);
    }

    /**
     * Add additional highlighting styles based on information not evident from
     * the AST.
     */
    private void addSemanticStyles(@NotNull Binding nb) {
        Def def = nb.getSingle();
        if (def == null || !def.hasName()) {
            return;
        }

        boolean isConst = CONSTANT.matcher(def.getName()).matches();
        switch (nb.getKind()) {
            case SCOPE:
                if (isConst) {
                    addSemanticStyle(def, StyleRun.Type.CONSTANT);
                }
                break;
            case VARIABLE:
                addSemanticStyle(def, isConst ? StyleRun.Type.CONSTANT : StyleRun.Type.IDENTIFIER);
                break;
            case PARAMETER:
                addSemanticStyle(def, StyleRun.Type.PARAMETER);
                break;
            case CLASS:
                addSemanticStyle(def, StyleRun.Type.TYPE_NAME);
                break;
        }
    }

    private void addSemanticStyle(@NotNull Def def, StyleRun.Type type) {
        String path = def.getFile();
        if (path != null) {
            addFileStyle(path, new StyleRun(type, def.getStart(), def.getLength()));
        }
    }


    private void processDiagnostic(@NotNull Diagnostic d) {
        StyleRun style = new StyleRun(StyleRun.Type.WARNING, d.start, d.end - d.start);
        style.message = d.msg;
        style.url = d.file;
        addFileStyle(d.file, style);
    }

    
    /**
     * Generate a URL for a reference to a binding.
     * @param binding the referenced binding
     * @param filename the path containing the reference, or null if there was an error
     */
    @Nullable
    private String toURL(@NotNull Binding binding, String filename) {
        Def def = binding.getSingle();

        if (binding.isBuiltin()) {
            return def.getURL();
        }

        String destPath = null;

        if (binding.getKind() == Binding.Kind.MODULE) {
            ModuleType mtype = binding.getType().asModuleType();
            if (mtype != null) {
                destPath = mtype.getFile();
            }
        } else {
            destPath = def.getFile();
        }

        if (destPath == null) {
            return null;
        }

        String anchor = "#" + binding.getQname();
        if (binding.getFirstFile().equals(filename)) {
            return anchor;
        }

        if (destPath.startsWith(rootPath)) {
            String relpath;
            if (filename != null) {
                relpath = Util.relativize(filename, destPath);
            } else {
                relpath = destPath;
            }

            if (relpath != null) {
                return relpath + ".html" + anchor;
            } else {
                return anchor;
            }
        } else {
            return "file://" + destPath + anchor;
        }
    }

}

@@ -2805,41 +2805,23 @@
 .get
-Qname() + %22 : %22 + binding.getType
+Type().toString
 ();%0A
@@ -3508,35 +3508,23 @@
 .get
-Qname() + %22 : %22 + b.getType
+Type().toString
 ());
@@ -3642,25 +3642,24 @@
 ayList%3C%3E();%0A
-%0A
         for 

stop displaying qname (because it's now absolute)