/*
 * 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.facebook.presto.spi.type;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Objects;

import static java.lang.String.format;
import static java.util.Collections.unmodifiableList;

public class TypeSignature
{
    private final String base;
    private final List<TypeSignature> parameters;
    private final List<Object> literalParameters;

    public TypeSignature(String base, List<TypeSignature> parameters, List<Object> literalParameters)
    {
        checkArgument(base != null, "base is null");
        this.base = base;
        checkArgument(!base.isEmpty(), "base is empty");
        checkArgument(validateName(base), "Bad characters in base type: %s", base);
        checkArgument(parameters != null, "parameters is null");
        checkArgument(literalParameters != null, "literalParameters is null");
        for (Object literal : literalParameters) {
            checkArgument(literal instanceof String || literal instanceof Long, "Unsupported literal type: %s", literal.getClass());
        }
        this.parameters = unmodifiableList(new ArrayList<>(parameters));
        this.literalParameters = unmodifiableList(new ArrayList<>(literalParameters));
    }

    private static boolean validateName(String name)
    {
        for (int i = 0; i < name.length(); i++) {
            char c = name.charAt(i);
            if (c == '<' || c == '>' || c == ',') {
                return false;
            }
        }
        return true;
    }

    @Override
    @JsonValue
    public String toString()
    {
        StringBuilder typeName = new StringBuilder(base);
        if (!parameters.isEmpty()) {
            typeName.append("<");
            boolean first = true;
            for (TypeSignature parameter : parameters) {
                if (!first) {
                    typeName.append(",");
                }
                first = false;
                typeName.append(parameter.toString());
            }
            typeName.append(">");
        }
        if (!literalParameters.isEmpty()) {
            typeName.append("(");
            boolean first = true;
            for (Object parameter : literalParameters) {
                if (!first) {
                    typeName.append(",");
                }
                first = false;
                if (parameter instanceof String) {
                    typeName.append("'").append(parameter).append("'");
                }
                else {
                    typeName.append(parameter.toString());
                }
            }
            typeName.append(")");
        }

        return typeName.toString();
    }

    public String getBase()
    {
        return base;
    }

    public List<TypeSignature> getParameters()
    {
        return parameters;
    }

    public List<Object> getLiteralParameters()
    {
        return literalParameters;
    }

    @JsonCreator
    public static TypeSignature parseTypeSignature(String signature)
    {
        if (!signature.contains("<") && !signature.contains("(")) {
            return new TypeSignature(signature, new ArrayList<TypeSignature>(), new ArrayList<>());
        }

        String baseName = null;
        List<TypeSignature> parameters = new ArrayList<>();
        List<Object> literalParameters = new ArrayList<>();
        int parameterStart = -1;
        int bracketCount = 0;
        boolean inLiteralParameters = false;

        for (int i = 0; i < signature.length(); i++) {
            char c = signature.charAt(i);
            if (c == '<') {
                if (bracketCount == 0) {
                    checkState(baseName == null, "Expected baseName to be null");
                    checkState(parameterStart == -1, "Expected parameter start to be -1");
                    baseName = signature.substring(0, i);
                    parameterStart = i + 1;
                }
                bracketCount++;
            }
            else if (c == '>') {
                bracketCount--;
                checkArgument(bracketCount >= 0, "Bad type signature: '%s'", signature);
                if (bracketCount == 0) {
                    checkArgument(parameterStart >= 0, "Bad type signature: '%s'", signature);
                    parameters.add(parseTypeSignature(signature.substring(parameterStart, i)));
                    parameterStart = i + 1;
                    if (i == signature.length() - 1) {
                        return new TypeSignature(baseName, parameters, literalParameters);
                    }
                }
            }
            else if (c == ',') {
                if (bracketCount == 1 && !inLiteralParameters) {
                    checkArgument(parameterStart >= 0, "Bad type signature: '%s'", signature);
                    parameters.add(parseTypeSignature(signature.substring(parameterStart, i)));
                    parameterStart = i + 1;
                }
                else if (bracketCount == 0 && inLiteralParameters) {
                    checkArgument(parameterStart >= 0, "Bad type signature: '%s'", signature);
                    literalParameters.add(parseLiteral(signature.substring(parameterStart, i)));
                    parameterStart = i + 1;
                }
            }
            else if (c == '(') {
                checkArgument(!inLiteralParameters, "Bad type signature: '%s'", signature);
                inLiteralParameters = true;
                if (bracketCount == 0) {
                    if (baseName == null) {
                        checkState(parameters.isEmpty(), "Expected no parameters");
                        checkState(parameterStart == -1, "Expected parameter start to be -1");
                        baseName = signature.substring(0, i);
                    }
                    parameterStart = i + 1;
                }
            }
            else if (c == ')') {
                checkArgument(inLiteralParameters, "Bad type signature: '%s'", signature);
                inLiteralParameters = false;
                if (bracketCount == 0) {
                    checkArgument(i == signature.length() - 1, "Bad type signature: '%s'", signature);
                    checkArgument(parameterStart >= 0, "Bad type signature: '%s'", signature);
                    literalParameters.add(parseLiteral(signature.substring(parameterStart, i)));
                    return new TypeSignature(baseName, parameters, literalParameters);
                }
            }
        }

        throw new IllegalArgumentException(format("Bad type signature: '%s'", signature));
    }

    private static Object parseLiteral(String literal)
    {
        if (literal.startsWith("'") || literal.endsWith("'")) {
            checkArgument(literal.startsWith("'") && literal.endsWith("'"), "Bad literal: '%s'", literal);
            return literal.substring(1, literal.length() - 1);
        }
        else {
            return Long.parseLong(literal);
        }
    }

    @Override
    public boolean equals(Object o)
    {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }

        TypeSignature other = (TypeSignature) o;

        return Objects.equals(this.base.toLowerCase(Locale.ENGLISH), other.base.toLowerCase(Locale.ENGLISH)) &&
                Objects.equals(this.parameters, other.parameters) &&
                Objects.equals(this.literalParameters, other.literalParameters);
    }

    @Override
    public int hashCode()
    {
        return Objects.hash(base.toLowerCase(Locale.ENGLISH), parameters, literalParameters);
    }

    private static void checkArgument(boolean argument, String format, Object...args)
    {
        if (!argument) {
            throw new IllegalArgumentException(format(format, args));
        }
    }

    private static void checkState(boolean argument, String format, Object...args)
    {
        if (!argument) {
            throw new IllegalStateException(format(format, args));
        }
    }
}

@@ -4282,26 +4282,22 @@
         
-checkState
+verify
 (baseNam
@@ -4356,34 +4356,30 @@
             
-checkState
+verify
 (parameterSt
@@ -6185,34 +6185,30 @@
             
-checkState
+verify
 (parameters.
@@ -6269,26 +6269,22 @@
         
-checkState
+verify
 (paramet
@@ -8516,18 +8516,14 @@
 oid 
-checkState
+verify
 (boo
@@ -8544,37 +8544,23 @@
  String 
-format, Object...args
+message
 )%0A    %7B%0A
@@ -8610,50 +8610,30 @@
 new 
-IllegalStateException(format(format, args)
+AssertionError(message
 );%0A 

Replace checkState with assert in TypeSignature