/*
 * Copyright 2012-2014 eBay Software Foundation and selendroid committers.
 * 
 * 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 io.selendroid.standalone.android.impl;

import com.android.ddmlib.AndroidDebugBridge;
import com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener;
import com.android.ddmlib.IDevice;
import io.selendroid.standalone.android.*;
import io.selendroid.standalone.exceptions.AndroidDeviceException;
import io.selendroid.standalone.exceptions.DeviceOfflineException;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

public class DefaultDeviceManager extends Thread implements IDeviceChangeListener, DeviceManager {
  private static final Logger log = Logger.getLogger(DefaultDeviceManager.class.getName());
  private String adbPath;
  private List<HardwareDeviceListener> deviceListeners = new ArrayList<HardwareDeviceListener>();
  private List<AndroidEmulatorPowerStateListener> emulatorPowerStateListener =
      new ArrayList<AndroidEmulatorPowerStateListener>();
  private Map<IDevice, DefaultHardwareDevice> connectedDevices =
      new HashMap<IDevice, DefaultHardwareDevice>();
  private Map<String, IDevice> virtualDevices = new HashMap<String, IDevice>();
  private AndroidDebugBridge bridge;
  private boolean shouldKeepAdbAlive;

  public DefaultDeviceManager(String adbPath, boolean shouldKeepAdbAlive) {
    this.adbPath = adbPath;
    this.shouldKeepAdbAlive = shouldKeepAdbAlive;
  }

  /**
   * Initializes the AndroidDebugBridge and registers the DefaultHardwareDeviceManager with the
   * AndroidDebugBridge device change listener.
   */
  protected void initializeAdbConnection() {
    // Get a device bridge instance. Initialize, create and restart.
    try {
      AndroidDebugBridge.init(false);
    } catch (IllegalStateException e) {
      if (!shouldKeepAdbAlive) {
        log.log(
            Level.WARNING,
            "AndroidDebugBridge may have been already initialized at this point. It is OK to proceed.",
            e);
      }
    }

    bridge = AndroidDebugBridge.getBridge();

    if (bridge == null) {
      bridge = AndroidDebugBridge.createBridge(adbPath, false);
    }
    IDevice[] devices = bridge.getDevices();

    AndroidDebugBridge.addDeviceChangeListener(this);

    // Add the existing devices to the list of devices we are tracking.
    if (devices.length > 0) {
      for (int i = 0; i < devices.length; i++) {
        deviceConnected(devices[i]);
        log.info("my devices: " + devices[i].getAvdName());
      }
    } else {
      long timeout = System.currentTimeMillis() + 2000;
      while ((devices = bridge.getDevices()).length == 0 && System.currentTimeMillis() < timeout) {
        try {
          Thread.sleep(50);
        } catch (InterruptedException e) {
          throw new RuntimeException(e);
        }
      }
      if (devices.length > 0) {
        for (int i = 0; i < devices.length; i++) {
          deviceConnected(devices[i]);
          log.info("my devices: " + devices[i].getAvdName());
        }
      }
    }

  }

  /**
   * Shutdown the AndroidDebugBridge and clean up all connected devices.
   */
  public void shutdown() {
    log.info("Notifying device listener about shutdown");
    for (HardwareDeviceListener listener : deviceListeners) {
      for (AndroidDevice device : connectedDevices.values()) {
        listener.onDeviceDisconnected(connectedDevices.get(device));
      }
    }
    log.info("Removing Device Manager listener from ADB");
    AndroidDebugBridge.removeDeviceChangeListener(this);
    if (!shouldKeepAdbAlive) {
      AndroidDebugBridge.disconnectBridge();
    }
    AndroidDebugBridge.terminate();
    log.info("stopping Device Manager");
    // TODO add thread interrupt and join handling
  }

  @Override
  public void deviceChanged(IDevice device, int changeMask) {
    // Only fire events if the phone properties are available
    if (IDevice.CHANGE_BUILD_INFO == changeMask && !device.isEmulator()) {
      for (HardwareDeviceListener listener : deviceListeners) {
        listener.onDeviceChanged(connectedDevices.get(device));
      }
    }
  }

  @Override
  public void deviceConnected(IDevice device) {
    if (device == null) {
      return;
    }
    if (device.isEmulator()) {
      String serial = device.getSerialNumber();
      Integer port = Integer.parseInt(serial.replace("emulator-", ""));
      String avdName = null;
      TelnetClient client = null;
      try {
        client = new TelnetClient(port);
        avdName = client.sendCommand("avd name");
      } catch (AndroidDeviceException e) {
        String logMessage = "Could not get avdName for device " + serial;
        log.log(Level.WARNING, logMessage, e);
      } finally {
        if (client != null) {
          client.close();
        }
      }
      virtualDevices.put(avdName, device);
      for (AndroidEmulatorPowerStateListener listener : emulatorPowerStateListener) {
        listener.onDeviceStarted(avdName, device.getSerialNumber());
      }
    } else {
      try {
        connectedDevices.put(device, new DefaultHardwareDevice(device));
        for (HardwareDeviceListener listener : deviceListeners) {
          listener.onDeviceConnected(connectedDevices.get(device));
        }
      } catch (DeviceOfflineException doe) {
        // skip devices that are currently "offline"
        log.warning("Skipping adding 'offline' hardware device");
      }
    }
  }

  @Override
  public void deviceDisconnected(IDevice device) {
    if (device == null) {
      return;
    }
    if (device.isEmulator()) {
      virtualDevices.remove(device.getAvdName());
      for (AndroidEmulatorPowerStateListener listener : emulatorPowerStateListener) {
        listener.onDeviceStopped(device.getSerialNumber());
      }
    } else if (connectedDevices.containsKey(device)) {
      for (HardwareDeviceListener listener : deviceListeners) {
        listener.onDeviceDisconnected(connectedDevices.get(device));
      }
      connectedDevices.remove(device);
    }
  }

  @Override
  public void registerListener(HardwareDeviceListener deviceListener) {
    this.deviceListeners.add(deviceListener);
  }

  @Override
  public void unregisterListener(HardwareDeviceListener deviceListener) {
    if (deviceListeners.contains(deviceListener)) {
      deviceListeners.remove(deviceListener);
    }
  }

  @Override
  public void registerListener(AndroidEmulatorPowerStateListener deviceListener) {
    this.emulatorPowerStateListener.add(deviceListener);
  }

  @Override
  public void unregisterListener(AndroidEmulatorPowerStateListener deviceListener) {
    if (emulatorPowerStateListener.contains(deviceListener)) {
      emulatorPowerStateListener.remove(deviceListener);
    }
  }

  @Override
  public void initialize(HardwareDeviceListener defaultHardwareListener,
                         AndroidEmulatorPowerStateListener emulatorListener) {
    registerListener(defaultHardwareListener);
    registerListener(emulatorListener);
    initializeAdbConnection();
  }

  public IDevice getVirtualDevice(String avdName) {
    return virtualDevices.get(avdName);
  }
}
