/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.atlas.query;

import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.StringJoiner;

class SelectClauseComposer {
    private static final String COUNT_STR = "count";
    private static final String MIN_STR = "min";
    private static final String MAX_STR = "max";
    private static final String SUM_STR = "sum";

    public boolean  isSelectNoop;

    private String[]            labels;
    private String[]            attributes; // Qualified names
    private String[]            items;
    private Map<String, String> itemAssignmentExprs;

    private int     countIdx = -1;
    private int     sumIdx   = -1;
    private int     maxIdx   = -1;
    private int     minIdx   = -1;
    private int     aggCount = 0;
    private int     introducedTypesCount = 0;
    private int     primitiveTypeCount = 0;

    public SelectClauseComposer() {}

    public static boolean isKeyword(String s) {
        return COUNT_STR.equals(s) ||
                MIN_STR.equals(s) ||
                MAX_STR.equals(s) ||
                SUM_STR.equals(s);
    }

    public String[] getItems() {
        return items;
    }

    public void setItems(final String[] items) {
        this.items = Arrays.copyOf(items, items.length);
    }

    public boolean updateAsApplicable(int currentIndex, String propertyForClause, String qualifiedName) {
        boolean ret = false;
        if (currentIndex == getCountIdx()) {
            ret = assign(currentIndex, COUNT_STR, GremlinClause.INLINE_COUNT.get(), GremlinClause.INLINE_ASSIGNMENT);
        } else if (currentIndex == getMinIdx()) {
            ret = assign(currentIndex, MIN_STR, propertyForClause,  GremlinClause.INLINE_ASSIGNMENT, GremlinClause.INLINE_MIN);
        } else if (currentIndex == getMaxIdx()) {
            ret = assign(currentIndex, MAX_STR, propertyForClause, GremlinClause.INLINE_ASSIGNMENT, GremlinClause.INLINE_MAX);
        } else if (currentIndex == getSumIdx()) {
            ret = assign(currentIndex, SUM_STR, propertyForClause, GremlinClause.INLINE_ASSIGNMENT, GremlinClause.INLINE_SUM);
        } else {
            attributes[currentIndex] = qualifiedName;
        }

        return ret;
    }

    public String[] getAttributes() {
        return attributes;
    }

    public void setAttributes(final String[] attributes) {
        this.attributes = Arrays.copyOf(attributes, attributes.length);
    }

    public boolean assign(int i, String qualifiedName, GremlinClause clause) {
        items[i] = clause.get(qualifiedName);
        return true;
    }

    public String[] getLabels() {
        return labels;
    }

    public void setLabels(final String[] labels) {
        this.labels = labels;
    }

    public boolean hasAssignmentExpr() {
        return itemAssignmentExprs != null && !itemAssignmentExprs.isEmpty();
    }

    public boolean onlyAggregators() {
        return hasAggregators() && aggCount == items.length;
    }

    public boolean hasAggregators() {
        return aggCount > 0;
    }

    public String getLabelHeader() {
        return getJoinedQuotedStr(getLabels());
    }

    public String getItemsString() {
        return String.join(",", getItems());
    }

    public String getAssignmentExprString(){
        return String.join(" ", itemAssignmentExprs.values());
    }

    public String getItem(int i) {
        return items[i];
    }

    public String getAttribute(int i) {
        return attributes[i];
    }

    public String getLabel(int i) {
        return labels[i];
    }

    public int getAttrIndex(String attr) {
        int ret = -1;
        for (int i = 0; i < attributes.length; i++) {
            if (attributes[i].equals(attr)) {
                ret = i;
                break;
            }
        }
        return ret;
    }

    private boolean assign(String item, String assignExpr) {
        if (itemAssignmentExprs == null) {
            itemAssignmentExprs = new LinkedHashMap<>();
        }

        itemAssignmentExprs.put(item, assignExpr);
        return true;
    }

    private boolean assign(int i, String s, String p1, GremlinClause clause) {
        items[i] = s;
        return assign(items[i], clause.get(s, p1));

    }

    private boolean assign(int i, String s, String p1, GremlinClause inline, GremlinClause clause) {
        items[i] = s;
        return assign(items[i], inline.get(s, clause.get(p1, p1)));
    }

    public int getCountIdx() {
        return countIdx;
    }

    public void setCountIdx(final int countIdx) {
        this.countIdx = countIdx;
        aggCount++;
    }

    public int getSumIdx() {
        return sumIdx;
    }

    public void setSumIdx(final int sumIdx) {
        this.sumIdx = sumIdx;
        aggCount++;
    }

    public int getMaxIdx() {
        return maxIdx;
    }

    public void setMaxIdx(final int maxIdx) {
        this.maxIdx = maxIdx;
        aggCount++;
    }

    public int getMinIdx() {
        return minIdx;
    }

    public void setMinIdx(final int minIdx) {
        this.minIdx = minIdx;
        aggCount++;
    }

    public boolean isAggregatorIdx(int idx) {
        return getMinIdx() == idx || getMaxIdx() == idx || getCountIdx() == idx || getSumIdx() == idx;
    }

    private String getJoinedQuotedStr(String[] elements) {
        StringJoiner joiner = new StringJoiner(",");
        Arrays.stream(elements)
              .map(x -> x.contains("'") ? "\"" + x + "\"" : "'" + x + "'")
              .forEach(joiner::add);
        return joiner.toString();
    }

    public boolean isAggregatorWithArgument(int i) {
        return i == getMaxIdx() || i == getMinIdx() || i == getSumIdx();
    }

    public void incrementTypesIntroduced() {
        introducedTypesCount++;
    }

    public int getIntroducedTypesCount() {
        return introducedTypesCount;
    }

    public void incrementPrimitiveType() {
        primitiveTypeCount++;
    }

    public boolean hasMultipleReferredTypes() {
        return getIntroducedTypesCount() > 1;
    }

    public boolean hasMixedAttributes() {
        return getIntroducedTypesCount() > 0 && getPrimitiveTypeCount() > 0;
    }

    private int getPrimitiveTypeCount() {
        return primitiveTypeCount;
    }
}
