/*
 * 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.index.sai;

import java.util.Collections;

import com.google.common.base.Objects;

import org.apache.cassandra.index.sai.disk.PrimaryKeyMap;
import org.apache.cassandra.index.sai.disk.SSTableIndex;
import org.apache.cassandra.index.sai.disk.format.IndexDescriptor;
import org.apache.cassandra.io.sstable.Descriptor;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.utils.Throwables;
import org.apache.cassandra.utils.concurrent.Ref;
import org.apache.cassandra.utils.concurrent.RefCounted;
import org.apache.cassandra.utils.concurrent.SharedCloseableImpl;

/**
 * An {@link SSTableContext} is created for an individual sstable and is shared across column indexes to track per-sstable
 * index files.
 * <p>
 * The {@link SSTableContext} will be released when receiving a sstable removed notification, but its shared copies in
 * individual {@link SSTableIndex}es will be released when in-flight read requests complete.
 */
public class SSTableContext extends SharedCloseableImpl
{
    public final SSTableReader sstable;
    public final IndexDescriptor indexDescriptor;
    public final PrimaryKeyMap.Factory primaryKeyMapFactory;

    private SSTableContext(SSTableReader sstable,
                           IndexDescriptor indexDescriptor,
                           PrimaryKeyMap.Factory primaryKeyMapFactory,
                           Cleanup cleanup)
    {
        super(cleanup);
        this.sstable = sstable;
        this.indexDescriptor = indexDescriptor;
        this.primaryKeyMapFactory = primaryKeyMapFactory;
    }

    private SSTableContext(SSTableContext copy)
    {
        super(copy);
        this.sstable = copy.sstable;
        this.indexDescriptor = copy.indexDescriptor;
        this.primaryKeyMapFactory = copy.primaryKeyMapFactory;
    }

    @SuppressWarnings({"resource", "RedundantSuppression"})
    public static SSTableContext create(SSTableReader sstable)
    {
        Ref<? extends SSTableReader> sstableRef = null;
        PrimaryKeyMap.Factory primaryKeyMapFactory = null;

        IndexDescriptor indexDescriptor = IndexDescriptor.create(sstable);
        try
        {
            sstableRef = sstable.tryRef();

            if (sstableRef == null)
            {
                throw new IllegalStateException("Couldn't acquire reference to the sstable: " + sstable);
            }

            primaryKeyMapFactory = indexDescriptor.newPrimaryKeyMapFactory(sstable);

            Cleanup cleanup = new Cleanup(primaryKeyMapFactory, indexDescriptor, sstableRef);

            return new SSTableContext(sstable, indexDescriptor, primaryKeyMapFactory, cleanup);
        }
        catch (Throwable t)
        {
            if (sstableRef != null)
            {
                sstableRef.release();
            }

            throw Throwables.unchecked(Throwables.close(t, Collections.singleton(primaryKeyMapFactory)));
        }
    }

    @Override
    public SSTableContext sharedCopy()
    {
        return new SSTableContext(this);
    }

    /**
     * Returns a new {@link SSTableIndex} for a per-column index
     */
    public SSTableIndex newSSTableIndex(IndexContext indexContext)
    {
        return indexDescriptor.newSSTableIndex(this, indexContext);
    }

    /**
     * @return descriptor of attached sstable
     */
    public Descriptor descriptor()
    {
        return sstable.descriptor;
    }

    /**
     * @return disk usage (in bytes) of per-sstable index files
     */
    public long diskUsage()
    {
        return indexDescriptor.sizeOnDiskOfPerSSTableComponents();
    }

    /**
     * @return number of open files per {@link SSTableContext} instance
     */
    public int openFilesPerSSTable()
    {
        return indexDescriptor.version.onDiskFormat().openFilesPerSSTableIndex(indexDescriptor.hasClustering());
    }

    @Override
    public String toString()
    {
        return "SSTableContext{" +
               "sstable=" + sstable.descriptor +
               '}';
    }

    @Override
    public boolean equals(Object o)
    {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        SSTableContext that = (SSTableContext) o;
        return Objects.equal(sstable.descriptor, that.sstable.descriptor);
    }

    @Override
    public int hashCode()
    {
        return Objects.hashCode(sstable.descriptor.hashCode());
    }

    private static class Cleanup implements RefCounted.Tidy
    {
        private final PrimaryKeyMap.Factory primaryKeyMapFactory;
        private final IndexDescriptor indexDescriptor;
        private final Ref<? extends SSTableReader> sstableRef;

        private Cleanup(PrimaryKeyMap.Factory primaryKeyMapFactory,
                        IndexDescriptor indexDescriptor,
                        Ref<? extends SSTableReader> sstableRef)
        {
            this.primaryKeyMapFactory = primaryKeyMapFactory;
            this.indexDescriptor = indexDescriptor;
            this.sstableRef = sstableRef;
        }

        @Override
        public void tidy()
        {
            Throwable t = sstableRef.ensureReleased(null);
            t = Throwables.close(t, Collections.singleton(primaryKeyMapFactory));

            Throwables.maybeFail(t);
        }

        @Override
        public String name()
        {
            return indexDescriptor.toString();
        }
    }
}
