// Copyright (c) 2009 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.sdk.internal;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;

import org.chromium.sdk.Script;
import org.chromium.sdk.internal.ScriptImpl.Descriptor;
import org.chromium.sdk.internal.protocol.data.ScriptHandle;
import org.chromium.sdk.internal.protocol.data.SomeHandle;
import org.chromium.sdk.internal.tools.v8.V8ProtocolUtil;

/**
 * Manages scripts known in the corresponding browser tab.
 */
public class ScriptManager {

  public interface Callback {
    /**
     * This method gets invoked for every script in the manager.
     *
     * @param script to process
     * @return whether other scripts should be processed. If false, the #forEach
     *         method terminates.
     */
    boolean process(Script script);
  }

  /**
   * Maps script id's to scripts.
   */
  private final Map<Long, ScriptImpl> idToScript =
      Collections.synchronizedMap(new HashMap<Long, ScriptImpl>());

  private final V8ContextFilter contextFilter;
  private final DebugSession debugSession;

  ScriptManager(V8ContextFilter contextFilter, DebugSession debugSession) {
    this.contextFilter = contextFilter;
    this.debugSession = debugSession;
  }

  /**
   * Adds a script using a "script" V8 response.
   *
   * @param scriptBody to add the script from
   * @param refs that contain the associated script debug context
   * @return the new script, or {@code null} if the response does not contain
   *         a valid script JSON
   */
  public synchronized Script addScript(ScriptHandle scriptBody, List<SomeHandle> refs) {

    ScriptImpl theScript = findById(V8ProtocolUtil.getScriptIdFromResponse(scriptBody));

    if (theScript == null) {
      Descriptor desc = Descriptor.forResponse(scriptBody, refs, contextFilter);
      if (desc == null) {
        return null;
      }
      theScript = new ScriptImpl(desc, debugSession);
      idToScript.put(desc.id, theScript);
    }
    if (scriptBody.source() != null) {
      setSourceCode(scriptBody, theScript);
    }

    return theScript;
  }

  /**
   * Associates a source received in a "source" V8 response with the given
   * script.
   *
   * @param body the JSON response body
   * @param script the script to associate the source with
   */
  private void setSourceCode(ScriptHandle body, ScriptImpl script) {
    String src = body.source();
    if (src == null) {
      return;
    }
    if (script != null) {
      script.setSource(src);
    }
  }

  /**
   * @param id of the script to find
   * @return the script with {@code id == ref} or {@code null} if none found
   */
  public ScriptImpl findById(Long id) {
    return idToScript.get(id);
  }

  /**
   * Determines whether all scripts added into this manager have associated
   * sources.
   *
   * @return whether all known scripts have associated sources
   */
  public boolean isAllSourcesLoaded() {
    final boolean[] result = new boolean[1];
    result[0] = true;
    forEach(new Callback() {
      public boolean process(Script script) {
        if (!script.hasSource()) {
          result[0] = false;
          return false;
        }
        return true;
      }
    });
    return result[0];
  }

  public Collection<Script> allScripts() {
    final Collection<Script> result = new HashSet<Script>();
    forEach(new Callback() {
      public boolean process(Script script) {
        result.add(script);
        return true;
      }
    });
    return result;
  }

  /**
   * This method allows running the same code for all scripts in the manager. All modifications
   * are blocked for this period of time.
   * @param callback to invoke for every script, until
   *        {@link Callback#process(Script)} returns {@code false}.
   */
  public synchronized void forEach(Callback callback) {
    for (Script script : idToScript.values()) {
      if (!callback.process(script)) {
        return;
      }
    }
  }

  public void reset() {
    idToScript.clear();
  }

  public V8ContextFilter getContextFilter() {
    return contextFilter;
  }
}
