/**
 * Copyright 2009 Google Inc.
 *
 * Licensed 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.waveprotocol.wave.examples.fedone.waveserver;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Maps;
import com.google.protobuf.ByteString;

import org.waveprotocol.wave.examples.fedone.crypto.WaveSigner;
import org.waveprotocol.wave.examples.fedone.waveserver.CertificateManager.SignatureResultListener;
import org.waveprotocol.wave.protocol.common.ProtocolSignedDelta;
import org.waveprotocol.wave.protocol.common.ProtocolWaveletDelta;

import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;


/**
 * Signs deltas passed to it in addDelta.
 * Obtain an instance of a Delta Signer using DeltaSignerFactory.
 *
 * Stores deltas in bundles. We will queue bundles that are in the process of being signed
 *
 * After a certain timeout, or after the bundle has reached
 * a maximum size, sign all deltas and call the listeners with the signed deltas.
 * A bundle is created on demand i.e. when a delta needs is to be added the first time,
 * or when a previous one has already been signed.
 *
 * TODO(balfanz): replace/augment the deltasToSign map to use proper streamauth bundles.
 *
 * @author jochen@google.com (Jochen Bekmann)
 */
class BundlingDeltaSigner implements DeltaSigner {

  private final int bundlingAccumulationDelayMs;
  private final WaveSigner signer;
  private final ScheduledExecutorService executorService;
  private DeltaBundle currentBundle;
  private final int maximumDeltaBundleSize;

  /** Stores deltas in a bundle. Start a timer when the first delta is added. */
  private class DeltaBundle {

    AtomicBoolean acceptMoreDeltas;

    Map<ByteStringMessage<ProtocolWaveletDelta>,
        CertificateManager.SignatureResultListener> deltasToSign = Maps.newHashMap();

    private ScheduledFuture<?> scheduledFuture;
    private final Runnable signingTask;

    DeltaBundle() {
      scheduledFuture = null;  // Will be null until a signingTask is scheduled.
      acceptMoreDeltas = new AtomicBoolean(true);
      signingTask = new Runnable() {
        @Override public void run() {
          signBundle();
        }
      };
    }

    boolean canAcceptMoreDeltas() {
      return acceptMoreDeltas.get();
    }

    /** Sign bundle, notify every listener with a signedDelta result. */
    synchronized private void signBundle() {
      for (Entry<ByteStringMessage<ProtocolWaveletDelta>, SignatureResultListener> entry :
        deltasToSign.entrySet()) {
        ProtocolSignedDelta.Builder signedDelta = ProtocolSignedDelta.newBuilder();
        ByteString deltaBytes =  entry.getKey().getByteString();
        signedDelta.setDelta(deltaBytes);
        signedDelta.addAllSignature(ImmutableList.of(signer.sign(deltaBytes.toByteArray())));
        entry.getValue().signatureResult(signedDelta.build());
      }
    }

    /**
     * Add the delta to the bundle. This may trigger signing now or later. The
     * resultListener will be called once the delta has been signed. May only
     * be called when canAcceptMoreDeltas is true.
     */
    synchronized void addDelta(ByteStringMessage<ProtocolWaveletDelta> delta,
        CertificateManager.SignatureResultListener resultListener) {
      Preconditions.checkState(acceptMoreDeltas.get());

      deltasToSign.put(delta, resultListener);
      if (deltasToSign.size() >= maximumDeltaBundleSize) {
        acceptMoreDeltas.set(false);
        // If there is a task scheduled, attempt to cancel, but do not interrupt if it's
        // already executing. If cancel() returns false the task has already been run.
        if (scheduledFuture == null || scheduledFuture.cancel(false)) {
          executorService.execute(signingTask);
        }
      } else if (scheduledFuture == null){
        scheduledFuture = executorService.schedule(signingTask, bundlingAccumulationDelayMs,
            TimeUnit.MILLISECONDS);
      }
    }
  }

  /**
   * Constructor.
   * @param executorService a ScheduledExecutorService, may be null if maximumDeltaBundleSize = 1
   * @param signer wave signer
   * @param bundlingAccumulationDelayMs hold deltas at most for this long
   * @param maximumDeltaBundleSize largest size for a bundle
   */
  BundlingDeltaSigner(ScheduledExecutorService executorService,
      WaveSigner signer,
      int maximumDeltaBundleSize,
      int bundlingAccumulationDelayMs) {
    this.executorService = executorService;
    this.signer = signer;
    this.currentBundle = null;
    Preconditions.checkArgument(maximumDeltaBundleSize > 1);
    this.maximumDeltaBundleSize = maximumDeltaBundleSize;
    Preconditions.checkArgument(bundlingAccumulationDelayMs > 0);
    this.bundlingAccumulationDelayMs = bundlingAccumulationDelayMs;
  }

  @Override
  synchronized public void sign(ByteStringMessage<ProtocolWaveletDelta> delta,
      CertificateManager.SignatureResultListener resultListener) {
    if (currentBundle == null || !currentBundle.canAcceptMoreDeltas()) {
      // If the old bundle has a pending task it will only be garbage collected once it's
      // signed all deltas because the scheduler has a handle to it.
      currentBundle = new DeltaBundle();
    }
    currentBundle.addDelta(delta, resultListener);
  }
}

@@ -854,32 +854,16 @@
 ol.wave.
-examples.fedone.
 crypto.W
@@ -998,39 +998,40 @@
 otocol.wave.
-protocol.common
+federation.Proto
 .ProtocolSig
@@ -1073,23 +1073,24 @@
 ave.
-protocol.common
+federation.Proto
 .Pro

Fix package rename issues in BundlingDeltaSigner.