/**
 * 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
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * 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 org.apache.commons.lang3.StringUtils;

import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.StringJoiner;

public 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";

    private final String[]                     labels;
    private final String[]                     attributes;
    private final String[]                     items;
    private final int                          countIdx;
    private final int                          sumIdx;
    private final int                          minIdx;
    private final int                          maxIdx;
    private final int                          aggCount;
    private final Map<Integer, AggregatorFlag> aggregatorFlags      = new HashMap<>();
    private final Set<Integer>                 isNumericAggregator  = new HashSet<>();
    private final Set<Integer>                 isPrimitiveAttr      = new HashSet<>();
    private final Map<String, String>          itemAssignmentExprs  = new LinkedHashMap<>();
    private       boolean                      isSelectNoop;
    private       int                          introducedTypesCount;

    public SelectClauseComposer(String[] labels, String[] attributes, String[] items, int countIdx, int sumIdx, int minIdx, int maxIdx) {
        this.labels     = labels;
        this.attributes = Arrays.copyOf(attributes, attributes.length);
        this.items      = Arrays.copyOf(items, items.length);
        this.countIdx   = countIdx;
        this.sumIdx     = sumIdx;
        this.minIdx     = minIdx;
        this.maxIdx     = maxIdx;

        int aggCount = 0;

        if (countIdx != -1) {
            this.aggregatorFlags.put(countIdx, AggregatorFlag.COUNT);
            aggCount++;
        }

        if (sumIdx != -1) {
            this.aggregatorFlags.put(sumIdx, AggregatorFlag.SUM);
            aggCount++;
        }

        if (maxIdx != -1) {
            this.aggregatorFlags.put(maxIdx, AggregatorFlag.MAX);
            aggCount++;
        }

        if (minIdx != -1) {
            this.aggregatorFlags.put(minIdx, AggregatorFlag.MIN);
            aggCount++;
        }

        this.aggCount = aggCount;
    }

    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 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);

            this.isNumericAggregator.add(currentIndex);
        } else if (currentIndex == getMinIdx()) {
            ret = assign(currentIndex, MIN_STR, propertyForClause, GremlinClause.INLINE_ASSIGNMENT, GremlinClause.INLINE_MIN);

            this.isNumericAggregator.add(currentIndex);
        } else if (currentIndex == getMaxIdx()) {
            ret = assign(currentIndex, MAX_STR, propertyForClause, GremlinClause.INLINE_ASSIGNMENT, GremlinClause.INLINE_MAX);

            this.isNumericAggregator.add(currentIndex);
        } else if (currentIndex == getSumIdx()) {
            ret = assign(currentIndex, SUM_STR, propertyForClause, GremlinClause.INLINE_ASSIGNMENT, GremlinClause.INLINE_SUM);

            this.isNumericAggregator.add(currentIndex);
        }

        attributes[currentIndex] = qualifiedName;

        return ret;
    }

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

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

        return true;
    }

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

    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 (!itemAssignmentExprs.isEmpty()) ? String.join(" ", itemAssignmentExprs.values()) : StringUtils.EMPTY;
    }

    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;
    }

    public int getCountIdx() {
        return countIdx;
    }

    public int getSumIdx() {
        return sumIdx;
    }

    public int getMaxIdx() {
        return maxIdx;
    }

    public int getMinIdx() {
        return minIdx;
    }

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

    public void incrementTypesIntroduced() {
        introducedTypesCount++;
    }

    public int getIntroducedTypesCount() {
        return introducedTypesCount;
    }

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

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

    public boolean getIsSelectNoop() {
        return this.isSelectNoop;
    }

    public void setIsSelectNoop(boolean isSelectNoop) {
        this.isSelectNoop = isSelectNoop;
    }

    public boolean isSumIdx(int idx) {
        return aggregatorFlags.get(idx) == AggregatorFlag.SUM;
    }

    public boolean isMinIdx(int idx) {
        return aggregatorFlags.get(idx) == AggregatorFlag.MIN;
    }

    public boolean isMaxIdx(int idx) {
        return aggregatorFlags.get(idx) == AggregatorFlag.MAX;
    }

    public boolean isCountIdx(int idx) {
        return aggregatorFlags.get(idx) == AggregatorFlag.COUNT;
    }

    public boolean isNumericAggregator(int idx) {
        return isNumericAggregator.contains(idx);
    }

    public boolean isPrimitiveAttribute(int idx) {
        return isPrimitiveAttr.contains(idx);
    }

    public void setIsPrimitiveAttr(int i) {
        this.isPrimitiveAttr.add(i);
    }

    private int getPrimitiveTypeCount() {
        return isPrimitiveAttr.size();
    }

    private boolean assign(String item, String assignExpr) {
        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)));
    }

    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 enum AggregatorFlag {
        NONE, COUNT, MIN, MAX, SUM
    }
}
