/*
 * Copyright © 2020 by Data Virtuality GmbH
 */
package org.teiid.query.sql.lang;

import org.teiid.core.TeiidRuntimeException;
import org.teiid.core.types.DataTypeManager;
import org.teiid.core.util.EquivalenceUtil;
import org.teiid.core.util.HashCodeUtil;
import org.teiid.query.QueryPlugin;
import org.teiid.query.sql.LanguageObject;
import org.teiid.query.sql.LanguageVisitor;
import org.teiid.query.sql.symbol.*;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author Roman Pedchenko
 * @date 2020.01.25
 */
public class Unpivot extends TableFunctionReference implements LanguageObject {
    private ElementSymbol col = null;
    private ElementSymbol forCol = null;
    private List<ElementSymbol> values = null;
    private List<ProjectedColumn> columns = null;
    private From from = null;
    private Select innerSel;
    private Map<Integer, String> foldColumns = null;
    private List<ElementSymbol> innerProjection;

    public Unpivot() {
    }

    public Unpivot(ElementSymbol col, ElementSymbol forCol, List<ElementSymbol> values, String name, From from) {
        this.col = col;
        this.forCol = forCol;
        this.values = values;
        setGroupSymbol(new GroupSymbol(name));
        this.from = from;
    }

    public static boolean sameCol(ElementSymbol item1, Symbol item2) {
        return item1.getShortName().equalsIgnoreCase(item2.getShortName());
    }

    public static Integer colIndex(List<? extends Expression> list, ElementSymbol item) {
        for (int i = 0; i < list.size(); i++) {
            Expression el = list.get(i);
            if (el instanceof Symbol && sameCol(item, (Symbol) el))
                return i;
        }
        return -1;
    }

    public void acceptVisitor(LanguageVisitor visitor) {
        visitor.visit(this);
    }

    @Override
    protected Unpivot cloneDirect() {
        Unpivot copy = new Unpivot();
        if (col != null)
            copy.col = col.clone();
        if (forCol != null)
            copy.forCol = forCol.clone();
        if (getGroupSymbol() != null)
            copy.setGroupSymbol(getGroupSymbol().clone());
        if (from != null)
            copy.setFrom((From) from.clone());
        copy.values = Util.deepClone(this.getValues(), ElementSymbol.class);
        if (this.columns != null) {
            copy.columns = new ArrayList<>();
            for (ProjectedColumn column : this.columns)
                copy.columns.add(column.copyTo(new ProjectedColumn()));
        }
        if (this.innerSel != null)
            copy.innerSel = this.innerSel.clone();
        if (this.foldColumns != null)
            copy.foldColumns = new HashMap<>(this.foldColumns);
        copy.innerProjection = Util.deepClone(this.innerProjection, ElementSymbol.class);
        return copy;
    }

    @SuppressWarnings("MethodDoesntCallSuperMethod")
    public Unpivot clone() {
        return cloneDirect();
    }

    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (!(obj instanceof Unpivot))
            return false;
        Unpivot other = (Unpivot) obj;
        return EquivalenceUtil.areEqual(getCol(), other.getCol()) &&
                EquivalenceUtil.areEqual(getForCol(), other.getForCol()) &&
                EquivalenceUtil.areEqual(getValues(), other.getValues()) &&
                EquivalenceUtil.areEqual(getGroupSymbol(), other.getGroupSymbol()) &&
                EquivalenceUtil.areEqual(getFrom(), other.getFrom());
    }

    public int hashCode() {
        int myHash = 0;
        myHash = HashCodeUtil.hashCode(myHash, this.getCol());
        myHash = HashCodeUtil.hashCode(myHash, this.getForCol());
        myHash = HashCodeUtil.hashCode(myHash, this.getValues());
        myHash = HashCodeUtil.hashCode(myHash, this.getGroupSymbol());
        myHash = HashCodeUtil.hashCode(myHash, this.getFrom());
        return myHash;
    }

    @Override
    public List<? extends ProjectedColumn> getColumns() {
        return columns;
    }

    public void initColumns(Select innerSel) {
        this.innerSel = innerSel;
        if (innerSel == null || !innerSel.isStar())
            throw new TeiidRuntimeException(QueryPlugin.Util.gs(QueryPlugin.Event.DVCORE301236));
        final Class<?> itype = getFoldingColumnsType();
        columns = new ArrayList<>();
        MultipleElementSymbol mul = (MultipleElementSymbol) innerSel.getSymbol(0);
        innerProjection = mul.getElementSymbols();
        columns.add(new ProjectedColumn(forCol.getName(), "string"));
        columns.add(new ProjectedColumn(col.getName(), DataTypeManager.getDataTypeName(itype)));
        foldColumns = new HashMap<>();
        for (int i = 0; i < innerProjection.size(); i++) {
            ElementSymbol el = innerProjection.get(i);
            if (el == null)
                continue;
            if (values.contains(el)) {
                foldColumns.put(i, el.getShortName());
                continue;
            }
            columns.add(new ProjectedColumn(el.getShortName(), DataTypeManager.getDataTypeName(el.getType())));
        }
    }

    private Class<?> getFoldingColumnsType() {
        final List<ElementSymbol> values = getValues();
        final ElementSymbol ivalue = values.get(0);
        final Class<?> itype = ivalue.getType();
        for (int i = 1; i < values.size(); i++) {
            final ElementSymbol ovalue = values.get(i);
            final Class<?> otype = ovalue.getType();
            if (!itype.equals(otype))
                throw new TeiidRuntimeException(QueryPlugin.Util.gs(QueryPlugin.Event.DVCORE301237, ivalue, itype, ovalue, otype));
        }
        return itype;
    }

    public ElementSymbol getCol() {
        return col;
    }

    public void setCol(ElementSymbol col) {
        this.col = col;
    }

    public ElementSymbol getForCol() {
        return forCol;
    }

    @SuppressWarnings("unused")
    public void setForCol(ElementSymbol forCol) {
        this.forCol = forCol;
    }

    public List<ElementSymbol> getValues() {
        return values;
    }

    public void setValues(List<ElementSymbol> values) {
        this.values = values;
    }

    public From getFrom() {
        return from;
    }

    public void setFrom(From from) {
        this.from = from;
    }

    public Select getInnerSel() {
        return innerSel;
    }

    public Map<Integer, String> getFoldColumns() {
        return foldColumns;
    }

    public List<ElementSymbol> getInnerProjection() {
        return innerProjection;
    }
}
