/*
 * Copyright (c) 2022 MiLaboratories Inc.
 *
 * 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.
 */
package com.milaboratory.util;

import java.util.*;
import java.util.stream.Collectors;

public final class StringUtil {
    private StringUtil() {
    }

    public static String spaces(int n) {
        return chars(' ', n);
    }

    public static String chars(char cc, int n) {
        char[] c = new char[n];
        Arrays.fill(c, cc);
        return String.valueOf(c);
    }

    /** Returns length of longest common substring */
    public static int longestCommonSubstring(CharSequence str1, CharSequence str2) {
        return longestCommonSubstring(str1, str2, str1.length(), str2.length());
    }

    /** Returns length of longest common substring */
    public static int longestCommonSubstring(CharSequence str1, CharSequence str2, int str1end, int str2end) {
        if (str1.length() == 0 || str2.length() == 0 || str1end == 0 || str2end == 0)
            return 0;

        // Create a table to store lengths of longest common suffixes of
        // substrings. Note that table[i][j] contains length of longest
        // common suffix of X[0..i-1] and Y[0..j-1]. The first row and
        // first column entries have no logical meaning, they are used only
        // for simplicity of program
        int[][] table = new int[str1end + 1][str2end + 1];
        int result = 0;  // To store length of the longest common substring

        // Following steps build table[m+1][n+1] in bottom up fashion
        for (int i = 0; i <= str1end; i++) {
            for (int j = 0; j <= str2end; j++) {
                if (i == 0 || j == 0)
                    table[i][j] = 0;
                else if (str1.charAt(i - 1) == str2.charAt(j - 1)) {
                    table[i][j] = table[i - 1][j - 1] + 1;
                    result = Integer.max(result, table[i][j]);
                } else
                    table[i][j] = 0;
            }
        }
        return result;
    }

    /** Returns true if specified string can be parsed as double */
    public static boolean isNumber(String str) {
        for (int i = 0; i < str.length(); i++) {
            if (!Character.isDigit(str.charAt(i)))
                return false;
        }

        try {
            Double.parseDouble(str);
            return true;
        } catch (NumberFormatException e) {
            return false;
        }
    }

    /**
     * Puts two lists in accordance to each other using LCS as match score.
     *
     * @return map of target_string=(query_string | null)
     */
    public static Map<String, String> matchLists(List<String> target, List<String> query) {
        final class pair {
            final String match;
            final double score;

            public pair(String match, double score) {
                this.match = match;
                this.score = score;
            }
        }

        Map<String, PriorityQueue<pair>> matched = new HashMap<>();
        for (String t : target) {
            PriorityQueue<pair> matchedForKey = new PriorityQueue<>(
                    Comparator.comparing(it -> -it.score));

            for (String q : query) {
                String a = t.toLowerCase();
                String b = q.toLowerCase();
                int match = StringUtil.longestCommonSubstring(a, b);
                double score = 2.0 * (0.5 + match) / (1 + a.length() + b.length());
                matchedForKey.add(new pair(q, score));
            }

            matched.put(t, matchedForKey);
        }

        List<String> unmatchedQ = new ArrayList<>(query);
        Map<String, String> r = new HashMap<>();
        for (Map.Entry<String, PriorityQueue<pair>> e : matched.entrySet().stream()
                .sorted(Comparator.comparing(kv ->
                        -kv.getValue().stream().mapToDouble(it -> it.score).max().orElse(0)))
                .collect(Collectors.toList())) {
            String t = e.getKey();
            PriorityQueue<pair> q = e.getValue();
            if (q.isEmpty()) {
                r.put(t, null);
                continue;
            }
            String m = null;
            while (!q.isEmpty()) {
                pair candidate = q.poll();
                boolean wasUnmatched = unmatchedQ.remove(candidate.match);
                if (wasUnmatched) {
                    m = candidate.match;
                    break;
                }
            }
            r.put(t, m);
        }

        return r;
    }
}
