/**
 * 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.model.supplement;

import org.waveprotocol.wave.model.adt.ObservableElementList;
import org.waveprotocol.wave.model.adt.docbased.DocumentBasedElementList;
import org.waveprotocol.wave.model.adt.docbased.Factory;
import org.waveprotocol.wave.model.adt.docbased.Initializer;
import org.waveprotocol.wave.model.document.ObservableMutableDocument;
import org.waveprotocol.wave.model.document.util.DocumentEventRouter;
import org.waveprotocol.wave.model.id.WaveletId;
import org.waveprotocol.wave.model.id.WaveletIdSerializer;
import org.waveprotocol.wave.model.util.CopyOnWriteSet;
import org.waveprotocol.wave.model.util.Serializer;

import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * A document based store for abuse data.
 *
 * Uses a DocumentBasedElementList, keeping elements as children of the root
 * element.
 *
 */
public final class DocumentBasedAbuseStore<N, E extends N> implements ObservableAbuseStore {

  /**
   * Converts WantedEvaluations to and from XML.
   */
  private static class ElementFactory<E> implements
      Factory<E, WantedEvaluation, WantedEvaluation> {

    @Override
    public WantedEvaluation adapt(DocumentEventRouter<? super E, E, ?> router,
        E element) {

      // We can do the conversion once at adapt() time because WantedEvaluations
      // are immutable. This also means we don't need a DocumentBased
      // implementation of WantedEvaluation.

      // TODO(user): work out what to do if wavelet id is missing.
      ObservableMutableDocument<? super E, E, ?> document = router.getDocument();
      WaveletId waveletId =
          WaveletIdSerializer.INSTANCE.fromString(document.getAttribute(element, WAVELET_ID_ATTR));
      boolean wanted =
          Serializer.BOOLEAN.fromString(document.getAttribute(element, WANTED_ATTR), false);
      double certainty =
          Serializer.DOUBLE.fromString(document.getAttribute(element, CERTAINTY_ATTR), 0.0);
      long timestamp =
          Serializer.LONG.fromString(document.getAttribute(element, TIMESTAMP_ATTR), 0L);
      boolean ignored =
          Serializer.BOOLEAN.fromString(document.getAttribute(element, IGNORED_ATTR), false);

      String agentIdentity = getNonNullAttribute(document, element, AGENT_ATTR);

      String comment = getNonNullAttribute(document, element, COMMENT_ATTR);

      String adderAddress = getNonNullAttribute(document, element, ADDER_ATTR);

      return new SimpleWantedEvaluation(waveletId, adderAddress, wanted, certainty, timestamp,
          agentIdentity, ignored, comment);
    }

    @Override
    public Initializer createInitializer(
        final WantedEvaluation wantedEvaluation) {
      return new Initializer() {
        @Override
        public void initialize(Map<String, String> target) {
          target.put(
              WAVELET_ID_ATTR,
              WaveletIdSerializer.INSTANCE.toString(wantedEvaluation.getWaveletId()));
          target.put(WANTED_ATTR, Serializer.BOOLEAN.toString(wantedEvaluation.isWanted()));
          target.put(CERTAINTY_ATTR, Serializer.DOUBLE.toString(wantedEvaluation.getCertainty()));
          target.put(TIMESTAMP_ATTR, Serializer.LONG.toString(wantedEvaluation.getTimestamp()));
          target.put(ADDER_ATTR, wantedEvaluation.getAdderAddress());
          target.put(AGENT_ATTR, wantedEvaluation.getAgentIdentity());
          target.put(COMMENT_ATTR, wantedEvaluation.getComment());
          target.put(IGNORED_ATTR, Serializer.BOOLEAN.toString(wantedEvaluation.isIgnored()));
        }
      };
    }

    /**
     * Convenience method for getting an attribute value, substituting an empty
     * string for a null attribute.
     *
     * @param document document to get from
     * @param element element to get from
     * @param attributeName name of attribute to get
     *
     * @return value of attribute, or empty string if the value is null
     */
    private String getNonNullAttribute(ObservableMutableDocument<? super E, E, ?> document,
        E element, String attributeName) {
      String attribute = document.getAttribute(element, attributeName);
      return attribute == null ? "" : attribute;
    }
  }

  public static final String AGENT_ATTR = "agent";
  public static final String CERTAINTY_ATTR = "certainty";
  public static final String COMMENT_ATTR = "comment";
  public static final String TIMESTAMP_ATTR = "timestamp";
  public static final String WANTED_ATTR = "wanted";
  public static final String WANTED_EVAL_TAG = "wanted";
  public static final String WAVELET_ID_ATTR = "wavelet_id";
  public static final String IGNORED_ATTR = "ignored";
  public static final String ADDER_ATTR = "adder";

  /**
   * Creates the document based store.
   *
   * If the document does not yet have an "evals" tag this method will first add
   * one as a child of the root.
   *
   * @param document the document on which to base the manifest
   */
  public static <N, E extends N> DocumentBasedAbuseStore<N, E> create(
      DocumentEventRouter<N, E, ?> router) {
    DocumentBasedElementList<E, WantedEvaluation, WantedEvaluation> list =
        DocumentBasedElementList.create(router,
            router.getDocument().getDocumentElement(), WANTED_EVAL_TAG,
            new ElementFactory<E>());
    return new DocumentBasedAbuseStore<N, E>(list);
  }

  /** XML backed list holding the Abuse Store data. */
  private final ObservableElementList<WantedEvaluation, WantedEvaluation> list;

  /** Contains all the listeners to this abuse store. */
  private final CopyOnWriteSet<Listener> listeners = CopyOnWriteSet.create();

  /** Constructor. */
  private DocumentBasedAbuseStore(
      ObservableElementList<WantedEvaluation, WantedEvaluation> list) {
    this.list = list;

    class ListListener implements ObservableElementList.Listener<WantedEvaluation> {
      @Override
      public void onValueAdded(WantedEvaluation entry) {
        triggerEvaluationAdded(entry);
      }

      @Override
      public void onValueRemoved(WantedEvaluation entry) {
        // This never happens, and, when it does, is safe to ignore.
      }
    }

    this.list.addListener(new ListListener());
  }

  @Override
  public void addListener(Listener listener) {
    listeners.add(listener);
  }

  @Override
  public void addWantedEvaluation(WantedEvaluation evaluation) {
    // Could check for duplicates at this point. In actual use, however,
    // duplicates are likely to be rare, and it is unclear whether scanning the
    // list of duplicates would be a win.
    list.add(evaluation);
  }

  @Override
  public Set<WantedEvaluation> getWantedEvaluations() {
    Set<WantedEvaluation> result = new HashSet<WantedEvaluation>();
    for (WantedEvaluation each : list.getValues()) {
      result.add(each);
    }
    return result;
  }

  @Override
  public void removeListener(Listener listener) {
    listeners.remove(listener);
  }

  /** Helper method - send to all listeners */
  private void triggerEvaluationAdded(WantedEvaluation newEvaluation) {
    for (Listener l : listeners) {
      l.onEvaluationAdded(newEvaluation);
    }
  }
}

@@ -6356,37 +6356,33 @@
     
-class ListListener implements
+this.list.addListener(new
  Obs
@@ -6426,16 +6426,18 @@
 luation%3E
+()
  %7B%0A     
@@ -6713,54 +6713,8 @@
    %7D
-%0A%0A    this.list.addListener(new ListListener()
 );%0A 

Fix SunJDK compile warning