/*
 * 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 com.google.gwt.dev.shell.remoteui;

import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.TreeLogger.Type;
import com.google.gwt.dev.ModuleHandle;
import com.google.gwt.dev.ui.DevModeUI;
import com.google.gwt.dev.ui.DoneCallback;
import com.google.gwt.dev.ui.DoneEvent;
import com.google.gwt.dev.ui.RestartServerCallback;
import com.google.gwt.dev.ui.RestartServerEvent;

import java.io.IOException;
import java.net.Socket;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * An implementation of a UI for the development mode server that sends UI
 * events over the network to a remote viewer. Also receives commands from the
 * remote viewer (such as a web server restart) and forwards the requests to the
 * development mode server.
 */
public class RemoteUI extends DevModeUI implements
    MessageTransport.ErrorCallback {

  private final String clientId;
  private final DevModeServiceRequestProcessor devModeRequestProcessor;
  private final List<ModuleHandle> modules = new ArrayList<ModuleHandle>();
  private final Object modulesLock = new Object();
  private final Socket transportSocket;
  private final MessageTransport transport;
  private ViewerServiceClient viewerServiceClient = null;
  private final List<String> cachedStartupUrls = new ArrayList<String>();

  public RemoteUI(String host, int port, String clientId) {
    try {
      this.clientId = clientId;
      transportSocket = new Socket(host, port);
      transportSocket.setKeepAlive(true);
      transportSocket.setTcpNoDelay(true);
      devModeRequestProcessor = new DevModeServiceRequestProcessor(this);
      transport = new MessageTransport(transportSocket.getInputStream(),
          transportSocket.getOutputStream(), devModeRequestProcessor, this);
      transport.start();
    } catch (UnknownHostException e) {
      throw new RuntimeException(e);
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }

  @Override
  public ModuleHandle getModuleLogger(String userAgent, String remoteSocket,
      String url, String tabKey, String moduleName, String sessionKey,
      String agentTag, byte[] agentIcon, Type logLevel) {

    final int logHandle = viewerServiceClient.addModuleLog(remoteSocket, url,
        tabKey, moduleName, sessionKey, agentTag, agentIcon);
    final ViewerServiceTreeLogger moduleLogger = new ViewerServiceTreeLogger(
        viewerServiceClient);
    moduleLogger.initLogHandle(logHandle);
    moduleLogger.setMaxDetail(getLogLevel());
    ModuleHandle handle = new ModuleHandle() {
      @Override
      public TreeLogger getLogger() {
        return moduleLogger;
      }

      @Override
      public void unload() {
        synchronized (modulesLock) {
          if (!modules.contains(this)) {
            return;
          }
        }
        try {
          viewerServiceClient.disconnectLog(logHandle);
        } finally {
          synchronized (modulesLock) {
            modules.remove(this);
          }
        }
      }
    };
    synchronized (modulesLock) {
      modules.add(handle);
    }

    if (moduleLogger.isLoggable(TreeLogger.SPAM)) {
      if (url != null) {
        moduleLogger.log(TreeLogger.SPAM, "Top URL: " + url);
      }

      moduleLogger.log(TreeLogger.SPAM, "User agent: " + userAgent);
      moduleLogger.log(TreeLogger.SPAM, "Remote socket: " + remoteSocket);
      if (tabKey != null) {
        moduleLogger.log(TreeLogger.SPAM, "Tab key: " + tabKey);
      }
      if (sessionKey != null) {
        moduleLogger.log(TreeLogger.SPAM, "Session key: " + sessionKey);
      }
    }

    return handle;
  }

  @Override
  public TreeLogger getWebServerLogger(String serverName, byte[] serverIcon) {
    return getConsoleLogger();
  }

  @Override
  public void moduleLoadComplete(boolean success) {
    // Until the RemoteMessage protobuf's backwards compatibility issues are
    // resolved, we send the startup URLs as part of the moduleLoadComplete so
    // they are not displayed before the server is ready to serve the modules at
    // the URLs.
    viewerServiceClient = new ViewerServiceClient(transport, getTopLogger());
    viewerServiceClient.initialize(clientId, cachedStartupUrls);
    viewerServiceClient.checkCapabilities();
  }

  @Override
  public void onResponseException(Exception e) {
    getTopLogger().log(TreeLogger.INFO,
        "An exception occured while attempting to send a response message.", e);
  }

  @Override
  public void onTermination(Exception e) {
    getConsoleLogger().log(TreeLogger.INFO,
        "Remote UI connection terminated due to exception: " + e);
    getConsoleLogger().log(TreeLogger.INFO,
        "Shutting down development mode server.");

    try {
      // Close the transport socket
      transportSocket.close();
    } catch (IOException e1) {
      // Purposely ignored
    }

    ((DoneCallback) getCallback(DoneEvent.getType())).onDone();
  }

  public boolean restartWebServer() {
    TreeLogger webServerLogger = getConsoleLogger();
    assert (webServerLogger != null);

    RestartServerCallback callback = ((RestartServerCallback) getCallback(RestartServerEvent.getType()));
    if (callback != null) {
      callback.onRestartServer(webServerLogger);
      return true;
    } else {
      // The server is still starting up
    }

    return false;
  }

  @Override
  public void setStartupUrls(Map<String, URL> urls) {
    for (URL url : urls.values()) {
      cachedStartupUrls.add(url.toExternalForm());
    }

    super.setStartupUrls(urls);
  }
}
