/*
 * 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.cassandra.cql3.terms;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;

import org.apache.cassandra.cql3.FunctionContext;
import org.apache.cassandra.cql3.VariableSpecifications;
import org.apache.cassandra.cql3.functions.Function;
import org.apache.cassandra.db.marshal.MultiElementType;
import org.apache.cassandra.serializers.MarshalException;

import static org.apache.cassandra.cql3.statements.RequestValidations.invalidRequest;

/**
 * Static classes for Terms representing individual values of multi-elements data types.
 */
public final class MultiElements
{
    private MultiElements()
    {
    }

    /**
     * The serialized elements of a multi-element type (collection, tuple, udt, ...)
     */
    public static class Value extends Term.Terminal
    {
        /**
         * The type represented by this {@code Value}
         */
        private final MultiElementType<?> type;

        /**
         * The serialized values of the elements composing this value.
         */
        private final List<ByteBuffer> elements;

        /**
         * Creates a {@code Value} from its serialized representation.
         *
         * @param value a serialized value from the specified type
         * @param type the value type
         * @return a {@code Value}
         */
        public static Value fromSerialized(ByteBuffer value, MultiElementType<?> type)
        {
            try
            {
                // We depend for SetType and MapType on the collections being without duplicated keys and sorted.
                return new Value(type, type.filterSortAndValidateElements(type.unpack(value)));
            }
            catch (MarshalException e)
            {
                throw invalidRequest(e.getMessage());
            }
        }

        public Value(MultiElementType<?> type, List<ByteBuffer> elements)
        {
            this.type = type;
            this.elements = elements;
        }

        @Override
        public ByteBuffer get()
        {
            return elements == null ? null : type.pack(elements);
        }

        @Override
        public List<ByteBuffer> getElements()
        {
            return elements;
        }
    }

    /**
     * The terms representing a multi-element value (collection, tuple, udt, ...) where at least one of the terms
     * represents a non-pure function or a bind marker.
     */
    public static class DelayedValue extends Term.NonTerminal
    {
        /**
         * The type of this value.
         */
        private final MultiElementType<?> type;

        /**
         * The terms representing the elements composing this value.
         */
        private final List<Term> elements;

        public DelayedValue(MultiElementType<?> type, List<Term> elements)
        {
            this.type = type;
            this.elements = elements;
        }

        @Override
        public void collectMarkerSpecification(VariableSpecifications boundNames, Object owner)
        {
            if (type.supportsElementBindMarkers())
            {
                for (int i = 0, m = elements.size(); i < m; i++)
                    elements.get(i).collectMarkerSpecification(boundNames, owner);
            }
        }

        @Override
        public Terminal bind(FunctionContext context)
        {
            try
            {
                List<ByteBuffer> buffers = new ArrayList<>(elements.size());
                for (Term t : elements)
                {
                    buffers.add(t.bindAndGet(context));
                }

                buffers = type.filterSortAndValidateElements(buffers);
                return new Value(type, buffers);
            }
            catch (MarshalException e)
            {
                throw invalidRequest(e.getMessage());
            }
        }

        @Override
        public boolean containsBindMarker()
        {
            if (type.supportsElementBindMarkers())
                return false;

            for (Term element : elements)
                if (element.containsBindMarker())
                    return true;
            return false;
        }

        @Override
        public void addFunctionsTo(List<Function> functions)
        {
            Terms.addFunctions(elements, functions);
        }
    }
}
