/*
 * 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.ignite.internal.processors.query.h2.opt;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicLong;
import org.h2.command.ddl.CreateTableData;
import org.h2.engine.Session;
import org.h2.index.BaseIndex;
import org.h2.index.Cursor;
import org.h2.index.Index;
import org.h2.index.IndexCondition;
import org.h2.index.IndexType;
import org.h2.index.SingleRowCursor;
import org.h2.message.DbException;
import org.h2.result.Row;
import org.h2.result.SearchRow;
import org.h2.result.SortOrder;
import org.h2.table.Column;
import org.h2.table.IndexColumn;
import org.h2.table.TableBase;
import org.h2.table.TableFilter;
import org.h2.value.Value;
import org.h2.value.ValueInt;
import org.jsr166.ConcurrentHashMap8;

/**
 * Meta table.
 */
public class GridH2MetaTable extends TableBase {
    /** */
    private static final int ID = 0;

    /** */
    private final MetaIndex index;

    /** */
    private final AtomicLong dataModificationId = new AtomicLong();

    /** */
    private final Set<Session> fakeExclusiveSet = Collections.newSetFromMap(
        new ConcurrentHashMap8<Session,Boolean>());

    /**
     * @param data Data.
     */
    public GridH2MetaTable(CreateTableData data) {
        super(data);

        ArrayList<Column> cols = data.columns;
        assert cols.size() == 4 : cols;

        Column id = cols.get(ID);
        assert "ID".equals(id.getName()) && id.getType() == Value.INT : cols;
        assert id.getColumnId() == ID;

        index = new MetaIndex();
    }

    /** {@inheritDoc} */
    @Override public Row getTemplateRow() {
        return new MetaRow();
    }

    /** {@inheritDoc} */
    @Override public SearchRow getTemplateSimpleRow(boolean singleColumn) {
        if (singleColumn)
            return GridH2RowFactory.create((Value)null);

        return new MetaRow();
    }

    /** {@inheritDoc} */
    @Override public boolean lock(Session session, boolean exclusive, boolean forceLockEvenInMvcc) {
        if (fakeExclusiveSet.contains(session))
            return true;

        if (exclusive)
            fakeExclusiveSet.add(session);

        return false;
    }

    /** {@inheritDoc} */
    @Override public void unlock(Session s) {
        fakeExclusiveSet.remove(s);
    }

    /** {@inheritDoc} */
    @Override public void close(Session session) {
        // No-op.
    }

    /** {@inheritDoc} */
    @Override public Index addIndex(Session session, String indexName, int indexId,
        IndexColumn[] cols, IndexType indexType, boolean create, String indexComment) {
        assert cols.length == 1 : "len: " + cols.length;

        int colId = cols[0].column.getColumnId();
        assert colId == ID : "colId: " + colId;

        return index;
    }

    /** {@inheritDoc} */
    @Override public void removeRow(Session session, Row row) {
        dataModificationId.incrementAndGet();
        index.remove(session, row);
    }

    /** {@inheritDoc} */
    @Override public void truncate(Session session) {
        dataModificationId.incrementAndGet();
        index.truncate(session);
    }

    /** {@inheritDoc} */
    @Override public void addRow(Session session, Row row) {
        dataModificationId.incrementAndGet();
        index.add(session, row);
    }

    /** {@inheritDoc} */
    @Override public void checkSupportAlter() {
        throw DbException.getUnsupportedException("alter");
    }

    /** {@inheritDoc} */
    @Override public String getTableType() {
        return SYSTEM_TABLE;
    }

    /** {@inheritDoc} */
    @Override public Index getScanIndex(Session session) {
        return index;
    }

    /** {@inheritDoc} */
    @Override public Index getUniqueIndex() {
        return index;
    }

    /** {@inheritDoc} */
    @Override public ArrayList<Index> getIndexes() {
        return null;
    }

    /** {@inheritDoc} */
    @Override public boolean isLockedExclusively() {
        return !fakeExclusiveSet.isEmpty();
    }

    /** {@inheritDoc} */
    @Override public boolean isLockedExclusivelyBy(Session s) {
        return fakeExclusiveSet.contains(s);
    }

    /** {@inheritDoc} */
    @Override public long getMaxDataModificationId() {
        return dataModificationId.get();
    }

    /** {@inheritDoc} */
    @Override public boolean isDeterministic() {
        return false;
    }

    /** {@inheritDoc} */
    @Override public boolean canGetRowCount() {
        return true;
    }

    /** {@inheritDoc} */
    @Override public boolean canDrop() {
        return false;
    }

    /** {@inheritDoc} */
    @Override public long getRowCount(Session session) {
        return index.getRowCount(session);
    }

    /** {@inheritDoc} */
    @Override public long getRowCountApproximation() {
        return index.getRowCountApproximation();
    }

    /** {@inheritDoc} */
    @Override public long getDiskSpaceUsed() {
        return 0;
    }

    /** {@inheritDoc} */
    @Override public void checkRename() {
        throw DbException.getUnsupportedException("rename");
    }

    /**
     * Get value row.
     */
    private static class MetaRow extends GridH2Row {
        /** */
        private Value v0;

        /** */
        private Value v1;

        /** */
        private Value v2;

        /** */
        private Value v3;

        /** {@inheritDoc} */
        @Override public int getColumnCount() {
            return 4;
        }

        /** {@inheritDoc} */
        @Override public Value getValue(int idx) {
            switch (idx) {
                case 0:
                    return v0;

                case 1:
                    return v1;

                case 2:
                    return v2;

                case 3:
                    return v3;

                default:
                    throw new IllegalStateException("Index: " + idx);
            }
        }

        /** {@inheritDoc} */
        @Override public void setValue(int idx, Value v) {
            switch (idx) {
                case 0:
                    v0 = v;

                    break;

                case 1:
                    v1 = v;

                    break;

                case 2:
                    v2 = v;

                    break;

                case 3:
                    v3 = v;

                    break;

                default:
                    throw new IllegalStateException("Index: " + idx);
            }
        }
    }

    /**
     * Met index.
     */
    private static class MetaIndex extends BaseIndex {
        /** */
        private final ConcurrentMap<ValueInt, GridH2Row> rows = new ConcurrentHashMap8<>();

        /** {@inheritDoc} */
        @Override public void checkRename() {
            throw DbException.getUnsupportedException("rename");
        }

        /** {@inheritDoc} */
        @Override public void close(Session session) {
            // No-op.
        }

        /**
         * @param row Row.
         * @return ID.
         */
        private static ValueInt id(SearchRow row) {
            Value id = row.getValue(ID);

            assert id != null;

            return (ValueInt)id;
        }

        /** {@inheritDoc} */
        @Override public void add(Session session, Row row) {
            rows.put(id(row), (GridH2Row)row);
        }

        /** {@inheritDoc} */
        @Override public void remove(Session session, Row row) {
            rows.remove(id(row), row);
        }

        /** {@inheritDoc} */
        @Override public Cursor find(Session session, SearchRow first, SearchRow last) {
            if (first == null || last == null || !Objects.equals(id(first), id(last)))
                return new GridH2Cursor(rows.values().iterator());

            return new SingleRowCursor(rows.get(id(first)));
        }

        /** {@inheritDoc} */
        @Override public double getCost(Session session, int[] masks, TableFilter[] filters,
            int filter, SortOrder sortOrder) {
            if ((masks[ID] & IndexCondition.EQUALITY) == IndexCondition.EQUALITY)
                return 1;

            return 1000 + rows.size();
        }

        /** {@inheritDoc} */
        @Override public void remove(Session session) {
            // No-op.
        }

        /** {@inheritDoc} */
        @Override public void truncate(Session session) {
            rows.clear();
        }

        /** {@inheritDoc} */
        @Override public boolean canGetFirstOrLast() {
            return false;
        }

        /** {@inheritDoc} */
        @Override public Cursor findFirstOrLast(Session session, boolean first) {
            throw new UnsupportedOperationException();
        }

        /** {@inheritDoc} */
        @Override public boolean needRebuild() {
            return false;
        }

        /** {@inheritDoc} */
        @Override public long getRowCount(Session session) {
            return rows.size();
        }

        /** {@inheritDoc} */
        @Override public long getRowCountApproximation() {
            return getRowCount(null);
        }

        /** {@inheritDoc} */
        @Override public long getDiskSpaceUsed() {
            return 0;
        }
    }
}
