/*
 * Copyright 2004-2019 H2 Group. Multiple-Licensed under the MPL 2.0,
 * and the EPL 1.0 (http://h2database.com/html/license.html).
 * Initial Developer: H2 Group
 */
package org.h2.expression.function;

import java.util.ArrayList;

import org.h2.api.ErrorCode;
import org.h2.engine.Database;
import org.h2.engine.Session;
import org.h2.expression.Expression;
import org.h2.expression.ExpressionColumn;
import org.h2.message.DbException;
import org.h2.result.LocalResult;
import org.h2.table.Column;
import org.h2.value.Value;
import org.h2.value.ValueCollectionBase;
import org.h2.value.ValueInt;
import org.h2.value.ValueNull;
import org.h2.value.ValueResultSet;

/**
 * Implementation of the functions TABLE(..), TABLE_DISTINCT(..), and
 * UNNEST(..).
 */
public class TableFunction extends Function {
    private final long rowCount;
    private Column[] columns;

    TableFunction(Database database, FunctionInfo info, long rowCount) {
        super(database, info);
        this.rowCount = rowCount;
    }

    @Override
    public Value getValue(Session session) {
        return getTable(session, false);
    }

    @Override
    protected void checkParameterCount(int len) {
        if (len < 1) {
            throw DbException.get(ErrorCode.INVALID_PARAMETER_COUNT_2, getName(), ">0");
        }
    }

    @Override
    public StringBuilder getSQL(StringBuilder builder) {
        if (info.type == UNNEST) {
            super.getSQL(builder);
            if (args.length < columns.length) {
                builder.append(" WITH ORDINALITY");
            }
            return builder;
        }
        builder.append(getName()).append('(');
        for (int i = 0; i < args.length; i++) {
            if (i > 0) {
                builder.append(", ");
            }
            builder.append(columns[i].getCreateSQL()).append('=');
            args[i].getSQL(builder);
        }
        return builder.append(')');
    }

    @Override
    public ValueResultSet getValueForColumnList(Session session,
            Expression[] nullArgs) {
        return getTable(session, true);
    }

    public void setColumns(ArrayList<Column> columns) {
        this.columns = columns.toArray(new Column[0]);
    }

    private ValueResultSet getTable(Session session, boolean onlyColumnList) {
        int totalColumns = columns.length;
        Expression[] header = new Expression[totalColumns];
        Database db = session.getDatabase();
        for (int i = 0; i < totalColumns; i++) {
            Column c = columns[i];
            ExpressionColumn col = new ExpressionColumn(db, c);
            header[i] = col;
        }
        LocalResult result = db.getResultFactory().create(session, header, totalColumns);
        if (!onlyColumnList && info.type == TABLE_DISTINCT) {
            result.setDistinct();
        }
        if (!onlyColumnList) {
            int len = totalColumns;
            boolean unnest = info.type == UNNEST, addNumber = false;
            if (unnest) {
                len = args.length;
                if (len < totalColumns) {
                    addNumber = true;
                }
            }
            Value[][] list = new Value[len][];
            int rows = 0;
            for (int i = 0; i < len; i++) {
                Value v = args[i].getValue(session);
                if (v == ValueNull.INSTANCE) {
                    list[i] = new Value[0];
                } else {
                    int type = v.getValueType();
                    if (type != Value.ARRAY && type != Value.ROW) {
                        v = v.convertTo(Value.ARRAY);
                    }
                    Value[] l = ((ValueCollectionBase) v).getList();
                    list[i] = l;
                    rows = Math.max(rows, l.length);
                }
            }
            for (int row = 0; row < rows; row++) {
                Value[] r = new Value[totalColumns];
                for (int j = 0; j < len; j++) {
                    Value[] l = list[j];
                    Value v;
                    if (l.length <= row) {
                        v = ValueNull.INSTANCE;
                    } else {
                        Column c = columns[j];
                        v = l[row];
                        if (!unnest) {
                            v = c.convert(v).convertPrecision(c.getType().getPrecision(), false)
                                    .convertScale(true, c.getType().getScale());
                        }
                    }
                    r[j] = v;
                }
                if (addNumber) {
                    r[len] = ValueInt.get(row + 1);
                }
                result.addRow(r);
            }
        }
        result.done();
        return ValueResultSet.get(result, Integer.MAX_VALUE);
    }

    public long getRowCount() {
        return rowCount;
    }

    @Override
    public Expression[] getExpressionColumns(Session session) {
        return getExpressionColumns(session, getValueForColumnList(session, null).getResult());
    }

    @Override
    public boolean isConstant() {
        for (Expression e : args) {
            if (!e.isConstant()) {
                return false;
            }
        }
        return true;
    }

}
