/**
 * 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.hadoop.hbase.wal;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.OptionalLong;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.HConstants;
import org.apache.hadoop.hbase.PrivateCellUtil;
import org.apache.hadoop.hbase.client.RegionInfo;
import org.apache.hadoop.hbase.regionserver.MultiVersionConcurrencyControl.WriteEntry;
import org.apache.hadoop.hbase.regionserver.wal.WALActionsListener;
import org.apache.hadoop.hbase.regionserver.wal.WALCoprocessorHost;
import org.apache.hadoop.hbase.util.CommonFSUtils;
import org.apache.yetus.audience.InterfaceAudience;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

// imports for things that haven't moved from regionserver.wal yet.

/**
 * No-op implementation of {@link WALProvider} used when the WAL is disabled.
 *
 * Should only be used when severe data loss is acceptable.
 *
 */
@InterfaceAudience.Private
class DisabledWALProvider implements WALProvider {

  private static final Logger LOG = LoggerFactory.getLogger(DisabledWALProvider.class);

  WAL disabled;

  @Override
  public void init(WALFactory factory, Configuration conf, String providerId) throws IOException {
    if (null != disabled) {
      throw new IllegalStateException("WALProvider.init should only be called once.");
    }
    if (null == providerId) {
      providerId = "defaultDisabled";
    }
    disabled = new DisabledWAL(new Path(CommonFSUtils.getWALRootDir(conf), providerId), conf, null);
  }

  @Override
  public List<WAL> getWALs() {
    List<WAL> wals = new ArrayList<>(1);
    wals.add(disabled);
    return wals;
  }

  @Override
  public WAL getWAL(RegionInfo region) throws IOException {
    return disabled;
  }

  @Override
  public void close() throws IOException {
    disabled.close();
  }

  @Override
  public void shutdown() throws IOException {
    disabled.shutdown();
  }

  private static class DisabledWAL implements WAL {
    protected final List<WALActionsListener> listeners = new CopyOnWriteArrayList<>();
    protected final Path path;
    protected final WALCoprocessorHost coprocessorHost;
    protected final AtomicBoolean closed = new AtomicBoolean(false);

    public DisabledWAL(final Path path, final Configuration conf,
        final List<WALActionsListener> listeners) {
      this.coprocessorHost = new WALCoprocessorHost(this, conf);
      this.path = path;
      if (null != listeners) {
        for(WALActionsListener listener : listeners) {
          registerWALActionsListener(listener);
        }
      }
    }

    @Override
    public void registerWALActionsListener(final WALActionsListener listener) {
      listeners.add(listener);
    }

    @Override
    public boolean unregisterWALActionsListener(final WALActionsListener listener) {
      return listeners.remove(listener);
    }

    @Override
    public Map<byte[], List<byte[]>> rollWriter() {
      if (!listeners.isEmpty()) {
        for (WALActionsListener listener : listeners) {
          listener.logRollRequested(WALActionsListener.RollRequestReason.ERROR);
        }
        for (WALActionsListener listener : listeners) {
          try {
            listener.preLogRoll(path, path);
          } catch (IOException exception) {
            LOG.debug("Ignoring exception from listener.", exception);
          }
        }
        for (WALActionsListener listener : listeners) {
          try {
            listener.postLogRoll(path, path);
          } catch (IOException exception) {
            LOG.debug("Ignoring exception from listener.", exception);
          }
        }
      }
      return null;
    }

    @Override
    public Map<byte[], List<byte[]>> rollWriter(boolean force) {
      return rollWriter();
    }

    @Override
    public void shutdown() {
      if(closed.compareAndSet(false, true)) {
        if (!this.listeners.isEmpty()) {
          for (WALActionsListener listener : this.listeners) {
            listener.logCloseRequested();
          }
        }
      }
    }

    @Override
    public void close() {
      shutdown();
    }

    @Override
    public long appendData(RegionInfo info, WALKeyImpl key, WALEdit edits) throws IOException {
      return append(info, key, edits, true);
    }

    @Override
    public long appendMarker(RegionInfo info, WALKeyImpl key, WALEdit edits)
      throws IOException {
      return append(info, key, edits, false);
    }

    private long append(RegionInfo info, WALKeyImpl key, WALEdit edits, boolean inMemstore)
        throws IOException {
      WriteEntry writeEntry = key.getMvcc().begin();
      if (!edits.isReplay()) {
        for (Cell cell : edits.getCells()) {
          PrivateCellUtil.setSequenceId(cell, writeEntry.getWriteNumber());
        }
      }
      key.setWriteEntry(writeEntry);
      if (!this.listeners.isEmpty()) {
        final long start = System.nanoTime();
        long len = 0;
        for (Cell cell : edits.getCells()) {
          len += PrivateCellUtil.estimatedSerializedSizeOf(cell);
        }
        final long elapsed = (System.nanoTime() - start) / 1000000L;
        for (WALActionsListener listener : this.listeners) {
          listener.postAppend(len, elapsed, key, edits);
        }
      }
      return -1;
    }

    @Override
    public void updateStore(byte[] encodedRegionName, byte[] familyName,
        Long sequenceid, boolean onlyIfGreater) { return; }

    @Override
    public void sync() {
      if (!this.listeners.isEmpty()) {
        for (WALActionsListener listener : this.listeners) {
          listener.postSync(0L, 0);
        }
      }
    }

    @Override
    public void sync(long txid) {
      sync();
    }

    @Override
    public Long startCacheFlush(final byte[] encodedRegionName, Map<byte[], Long>
        flushedFamilyNamesToSeq) {
      return startCacheFlush(encodedRegionName, flushedFamilyNamesToSeq.keySet());
    }

    @Override
    public Long startCacheFlush(final byte[] encodedRegionName, Set<byte[]> flushedFamilyNames) {
      if (closed.get()) return null;
      return HConstants.NO_SEQNUM;
    }

    @Override
    public void completeCacheFlush(final byte[] encodedRegionName) {
    }

    @Override
    public void abortCacheFlush(byte[] encodedRegionName) {
    }

    @Override
    public WALCoprocessorHost getCoprocessorHost() {
      return coprocessorHost;
    }

    @Override
    public long getEarliestMemStoreSeqNum(byte[] encodedRegionName) {
      return HConstants.NO_SEQNUM;
    }

    @Override
    public long getEarliestMemStoreSeqNum(byte[] encodedRegionName, byte[] familyName) {
      return HConstants.NO_SEQNUM;
    }

    @Override
    public String toString() {
      return "WAL disabled.";
    }

    @Override
    public OptionalLong getLogFileSizeIfBeingWritten(Path path) {
      return OptionalLong.empty();
    }
  }

  @Override
  public long getNumLogFiles() {
    return 0;
  }

  @Override
  public long getLogFileSize() {
    return 0;
  }

  @Override
  public void addWALActionsListener(WALActionsListener listener) {
    disabled.registerWALActionsListener(listener);
  }
}