/*
 * 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.db.virtual;

import java.util.Date;
import java.util.Iterator;
import java.util.List;

import com.google.common.annotations.VisibleForTesting;

import org.apache.cassandra.config.CassandraRelevantProperties;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.marshal.Int32Type;
import org.apache.cassandra.db.marshal.TimestampType;
import org.apache.cassandra.db.marshal.UTF8Type;
import org.apache.cassandra.dht.LocalPartitioner;
import org.apache.cassandra.schema.TableMetadata;

import ch.qos.logback.classic.spi.LoggingEvent;

/**
 * Virtual table for holding Cassandra logs. Entries to this table are added via log appender.
 * <p>
 * The virtual table is bounded in its size. If a new log message is appended to virtual table,
 * the oldest one is removed.
 * <p>
 * This virtual table can be truncated.
 * <p>
 * This table does not enable {@code ALLOW FILTERING} implicitly.
 *
 * @see <a href="https://issues.apache.org/jira/browse/CASSANDRA-18238">CASSANDRA-18238</a>
 * @see org.apache.cassandra.utils.logging.VirtualTableAppender
 */
public final class LogMessagesTable extends AbstractLoggerVirtualTable<LoggingEvent>
{
    public static final int LOGS_VIRTUAL_TABLE_DEFAULT_ROWS = 50_000;
    public static final int LOGS_VIRTUAL_TABLE_MAX_ROWS = 100_000;

    public static final String TABLE_NAME = "system_logs";
    private static final String TABLE_COMMENT = "Cassandra logs";

    public static final String TIMESTAMP_COLUMN_NAME = "timestamp";
    public static final String LOGGER_COLUMN_NAME = "logger";
    public static final String ORDER_IN_MILLISECOND_COLUMN_NAME = "order_in_millisecond";
    public static final String LEVEL_COLUMN_NAME = "level";
    public static final String MESSAGE_COLUMN_NAME = "message";

    LogMessagesTable(String keyspace)
    {
        this(keyspace, resolveBufferSize(CassandraRelevantProperties.LOGS_VIRTUAL_TABLE_MAX_ROWS.getInt(),
                                         LOGS_VIRTUAL_TABLE_MAX_ROWS,
                                         LOGS_VIRTUAL_TABLE_DEFAULT_ROWS));
    }

    @VisibleForTesting
    LogMessagesTable(String keyspace, int size)
    {
        super(TableMetadata.builder(keyspace, TABLE_NAME)
                           .comment(TABLE_COMMENT)
                           .kind(TableMetadata.Kind.VIRTUAL)
                           .partitioner(new LocalPartitioner(TimestampType.instance))
                           .addPartitionKeyColumn(TIMESTAMP_COLUMN_NAME, TimestampType.instance)
                           .addClusteringColumn(ORDER_IN_MILLISECOND_COLUMN_NAME, Int32Type.instance)
                           .addRegularColumn(LOGGER_COLUMN_NAME, UTF8Type.instance)
                           .addRegularColumn(LEVEL_COLUMN_NAME, UTF8Type.instance)
                           .addRegularColumn(MESSAGE_COLUMN_NAME, UTF8Type.instance).build(),
              size);
    }

    @Override
    public List<LoggingEvent> getMessages(LoggingEvent event)
    {
        return List.of(event);
    }

    @Override
    public DataSet data()
    {
        SimpleDataSet result = new SimpleDataSet(metadata(), DecoratedKey.comparator.reversed());

        synchronized (buffer)
        {
            long milliSecondsOfPreviousLog = 0;
            long milliSecondsOfCurrentLog;

            int index = 0;

            Iterator<LoggingEvent> iterator = buffer.listIterator();
            while (iterator.hasNext())
            {
                LoggingEvent log = iterator.next();

                milliSecondsOfCurrentLog = log.getTimeStamp();
                if (milliSecondsOfPreviousLog == milliSecondsOfCurrentLog)
                    ++index;
                else
                    index = 0;

                milliSecondsOfPreviousLog = milliSecondsOfCurrentLog;

                result.row(new Date(milliSecondsOfCurrentLog), index)
                      .column(LOGGER_COLUMN_NAME, log.getLoggerName())
                      .column(LEVEL_COLUMN_NAME, log.getLevel().toString())
                      .column(MESSAGE_COLUMN_NAME, log.getFormattedMessage());
            }
        }

        return result;
    }
}
