package org.h2.util;

import java.util.HashSet;
import java.util.Set;
import java.util.regex.Matcher;
import org.h2.engine.Session;
import org.h2.expression.Expression;


public class ColumnNamer {

    private static final String DEFAULT_COLUMN_NAME = "DEFAULT";

    private ColumnNamerConfiguration configuration;
    private Session session;
    private final Set<String> existingColumnNames = new HashSet<>();

    public ColumnNamer(Session session) {
        this.session = session;
        if(this.session!=null && this.session.getColumnNamerConfiguration()!=null){
            // use original from session
            this.configuration = this.session.getColumnNamerConfiguration();
        }
        else{
            // detached namer, create new
            this.configuration = ColumnNamerConfiguration.getDefault();
            if(session!=null){
                session.setColumnNamerConfiguration(this.configuration);
            }
        }
    }
    /**
     * Create a standardized column name that isn't null and doesn't have a CR/LF in it.
     * @param expr the column expression
     * @param indexOfColumn index of column in below array
     */
    public String getColumnName(Expression expr, int indexOfColumn) {
        return getColumnName(expr,indexOfColumn,(String) null);
    }
    /**
     * Create a standardized column name that isn't null and doesn't have a CR/LF in it.
     * @param columnExp the column expression
     * @param indexOfColumn index of column in below array
     * @param columnNameOverides array of overriding column names
     * @return the new column name
     */
    public String getColumnName(Expression columnExp, int indexOfColumn, String[] columnNameOverides){
        String columnNameOverride = null;
        if (columnNameOverides != null && columnNameOverides.length > indexOfColumn){
            columnNameOverride = columnNameOverides[indexOfColumn];
        }
        return getColumnName(columnExp, indexOfColumn, columnNameOverride);
    }

    /**
     * Create a standardized column name that isn't null and doesn't have a CR/LF in it.
     * @param columnExp the column expression
     * @param indexOfColumn index of column in below array
     * @param columnNameOverride single overriding column name
     * @return the new column name
     */
    public String getColumnName(Expression columnExp, int indexOfColumn, String columnNameOverride) {
        // try a name from the column name override
        String columnName = null;
        if (columnNameOverride != null){
            columnName = columnNameOverride;
            if(!isAllowableColumnName(columnName)){
                columnName = fixColumnName(columnName);
            }
            if(!isAllowableColumnName(columnName)){
                columnName = null;
            }
        }
        // try a name from the column alias
        if (columnName==null && columnExp.getAlias()!=null && !DEFAULT_COLUMN_NAME.equals(columnExp.getAlias())){
            columnName = columnExp.getAlias();
            if(!isAllowableColumnName(columnName)){
                columnName = fixColumnName(columnName);
            }
            if(!isAllowableColumnName(columnName)){
                columnName = null;
            }
        }
        // try a name derived from the column expression SQL
        if (columnName==null && columnExp.getColumnName()!=null && !DEFAULT_COLUMN_NAME.equals(columnExp.getColumnName())){
             columnName =  columnExp.getColumnName();
             if(!isAllowableColumnName(columnName)){
                 columnName = fixColumnName(columnName);
             }
             if(!isAllowableColumnName(columnName)){
                 columnName = null;
             }
        }
        // try a name derived from the column expression plan SQL
        if (columnName==null && columnExp.getSQL()!=null && !DEFAULT_COLUMN_NAME.equals(columnExp.getSQL())){
             columnName =  columnExp.getSQL();
             if(!isAllowableColumnName(columnName)){
                 columnName = fixColumnName(columnName);
             }
             if(!isAllowableColumnName(columnName)){
                 columnName = null;
             }
        }
        // go with a innocuous default name pattern
        if (columnName==null){
            columnName =  configuration.getDefaultColumnNamePattern().replace("$$", ""+(indexOfColumn+1));
        }
        if(existingColumnNames.contains(columnName) && configuration.isGenerateUniqueColumnNames()){
            columnName = generateUniqueName(columnName);
        }
        existingColumnNames.add(columnName);
        return columnName;
    }

    private String generateUniqueName(String columnName) {
        String newColumnName = columnName;
        int loopCount = 2;
        while(existingColumnNames.contains(newColumnName)){

            String loopCountString = "_"+loopCount;
            newColumnName = columnName.substring(0,Math.min(columnName.length(), configuration.getMaxIdentiferLength()-loopCountString.length()))+loopCountString;

            loopCount++;
        }
        return newColumnName;
    }

    public boolean isAllowableColumnName(String proposedName){

        // check null
        if (proposedName == null){
            return false;
        }
        // check size limits
        if (proposedName.length() > configuration.getMaxIdentiferLength() || proposedName.length()==0){
            return false;
        }
        Matcher match = configuration.getCompiledRegularExpressionMatchAllowed().matcher(proposedName);
        if(!match.matches()){
            return false;
        }
        return true;
    }

    private String fixColumnName(String proposedName) {
        Matcher match = configuration.getCompiledRegularExpressionMatchDisallowed().matcher(proposedName);
        proposedName = match.replaceAll("");

        // check size limits - then truncate
        if (proposedName.length() > configuration.getMaxIdentiferLength()){
            proposedName=proposedName.substring(0, configuration.getMaxIdentiferLength());
        }

        return proposedName;
    }

    public ColumnNamerConfiguration getConfiguration() {
        return configuration;
    }

}
