package org.chromium.sdk.internal.v8native;

import java.util.Collections;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.chromium.sdk.DebugContext;
import org.chromium.sdk.DebugEventListener;
import org.chromium.sdk.RelayOk;
import org.chromium.sdk.SyncCallback;
import org.chromium.sdk.internal.ScriptBase;
import org.chromium.sdk.internal.liveeditprotocol.LiveEditResult;
import org.chromium.sdk.internal.protocolparser.JsonProtocolParseException;
import org.chromium.sdk.internal.v8native.V8Helper.ScriptLoadCallback;
import org.chromium.sdk.internal.v8native.protocol.input.ChangeLiveBody;
import org.chromium.sdk.internal.v8native.protocol.input.SuccessCommandResponse;
import org.chromium.sdk.internal.v8native.protocol.input.data.ScriptHandle;
import org.chromium.sdk.internal.v8native.protocol.input.data.SomeHandle;
import org.chromium.sdk.internal.v8native.protocol.output.ChangeLiveMessage;
import org.chromium.sdk.internal.v8native.value.HandleManager;

public class ScriptImpl extends ScriptBase<Long> {
  /** The class logger. */
  private static final Logger LOGGER = Logger.getLogger(ScriptImpl.class.getName());

  private final DebugSession debugSession;

  public ScriptImpl(Descriptor<Long> descriptor, DebugSession debugSession) {
    super(descriptor);
    this.debugSession = debugSession;
  }

  @Override
  public RelayOk setSourceOnRemote(String newSource, UpdateCallback callback,
      SyncCallback syncCallback) {
    V8CommandProcessor.V8HandlerCallback v8Callback = createScriptUpdateCallback(callback, false);
    return debugSession.sendMessageAsync(new ChangeLiveMessage(getId(), newSource, Boolean.FALSE),
        true, v8Callback, syncCallback);
  }

  @Override
  public RelayOk previewSetSource(String newSource, UpdateCallback callback,
      SyncCallback syncCallback) {
    V8CommandProcessor.V8HandlerCallback v8Callback = createScriptUpdateCallback(callback, true);
    return debugSession.sendMessageAsync(new ChangeLiveMessage(getId(), newSource, Boolean.TRUE),
        true, v8Callback, syncCallback);
  }

  private V8CommandProcessor.V8HandlerCallback createScriptUpdateCallback(
      final UpdateCallback callback, final boolean previewOnly) {
    return new V8CommandCallbackBase() {
      @Override
      public void success(SuccessCommandResponse successResponse) {
        ChangeLiveBody body;
        try {
          body = successResponse.body().asChangeLiveBody();
        } catch (JsonProtocolParseException e) {
          throw new RuntimeException(e);
        }

        LiveEditResult resultDescription = body.getResultDescription();
        if (!previewOnly) {
          ScriptLoadCallback scriptCallback = new ScriptLoadCallback() {
            @Override
            public void failure(String message) {
              LOGGER.log(Level.SEVERE,
                  "Failed to reload script after LiveEdit script update; " + message);
            }

            @Override
            public void success() {
              DebugEventListener listener = debugSession.getDebugEventListener();
              if (listener != null) {
                listener.scriptContentChanged(ScriptImpl.this);
              }
            }
          };
          V8Helper.reloadScriptAsync(debugSession, Collections.singletonList(getId()),
              scriptCallback, null);

          if (body.stepin_recommended() == Boolean.TRUE) {
            DebugContext debugContext = debugSession.getContextBuilder().getCurrentDebugContext();
            if (debugContext == null) {
              // We may have already issued 'continue' since the moment that change live command
              // was sent so the context was dropped. Ignore this case.
            } else {
              debugContext.continueVm(DebugContext.StepAction.IN, 0, null);
            }
          } else {
            if (resultDescription != null && resultDescription.stack_modified()) {
              debugSession.recreateCurrentContext();
            }
          }
        }

        if (callback != null) {
          callback.success(body.getChangeLog(),
              UpdateResultParser.wrapChangeDescription(resultDescription));
        }
      }

      @Override
      public void failure(String message) {
        callback.failure(message);
      }
    };
  }

  public static Long getScriptId(HandleManager handleManager, long scriptRef) {
    SomeHandle handle = handleManager.getHandle(scriptRef);
    if (handle == null) {
      return -1L; // not found
    }
    ScriptHandle scriptHandle;
    try {
      scriptHandle = handle.asScriptHandle();
    } catch (JsonProtocolParseException e) {
      throw new RuntimeException(e);
    }
    return scriptHandle.id();
  }
}
