/*
 * 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 gnu.trove.map.hash.TCharIntHashMap;

public final class ParseUtil {
    private ParseUtil() {
    }

    public static String[] splitWithBrackets(String string, char splitChar, String brackets) {
        return splitWithBrackets(string, splitChar, new BracketsInfo(brackets));
    }

    public static String[] splitWithBrackets(String string, char splitChar, BracketsInfo brackets) {
        IntArrayList splitPoints = new IntArrayList();
        splitPoints.push(-1);
        BracketsProcessor bracketsProcessor = brackets.createBracketsProcessor();
        for (int i = 0; i < string.length(); ++i) {
            char c = string.charAt(i);
            if (!bracketsProcessor.process(c)) {
                if (c == splitChar && bracketsProcessor.getDepth() == 0)
                    splitPoints.push(i);
            }
        }
        bracketsProcessor.finish();
        splitPoints.push(string.length());
        int size = splitPoints.size() - 1;
        if (size == 1 && splitPoints.get(0) == -1 && splitPoints.get(1) == 0)
            return new String[0];
        String[] result = new String[size];
        for (int i = 0; i < size; ++i)
            result[i] = string.substring(splitPoints.get(i) + 1, splitPoints.get(i + 1));
        return result;
    }

    public static final class BracketsInfo {
        private final TCharIntHashMap bracketsMap = new TCharIntHashMap();

        public BracketsInfo(String brackets) {
            if (brackets.length() % 2 != 0)
                throw new IllegalArgumentException();
            for (int i = brackets.length() / 2 - 1; i >= 0; --i) {
                bracketsMap.put(brackets.charAt(i * 2), i + 1);
                bracketsMap.put(brackets.charAt(i * 2 + 1), -i - 1);
            }
        }

        BracketsProcessor createBracketsProcessor() {
            return new BracketsProcessor(bracketsMap);
        }
    }

    private static final class BracketsProcessor {
        private final TCharIntHashMap bracketsMap;
        IntArrayList types = new IntArrayList();

        private BracketsProcessor(TCharIntHashMap bracketsMap) {
            this.bracketsMap = bracketsMap;
        }

        public boolean process(char c) {
            int v = bracketsMap.get(c);
            if (v == 0)
                return false;
            else {
                if (v < 0) {
                    if (types.size() == 0)
                        throw new ParserException("Closing bracket '" + c + "' before any opening bracket.");
                    if (types.pop() != -v)
                        throw new ParserException("Unbalanced bracket '" + c + "'.");
                    return true;
                } else {
                    types.push(v);
                    return true;
                }
            }
        }

        public void finish() {
            if (getDepth() != 0)
                throw new ParserException("Unbalanced brackets.");
        }

        public int getDepth() {
            return types.size();
        }
    }
}
