/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * 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.android.tradefed.device;

import com.android.annotations.VisibleForTesting;
import com.android.ddmlib.IDevice;
import com.android.tradefed.device.DeviceManager.FastbootDevice;
import com.android.tradefed.device.IDeviceSelection.BaseDeviceType;
import com.android.tradefed.device.cloud.ManagedRemoteDevice;
import com.android.tradefed.device.cloud.NestedDeviceStateMonitor;
import com.android.tradefed.device.cloud.NestedRemoteDevice;
import com.android.tradefed.device.cloud.RemoteAndroidVirtualDevice;
import com.android.tradefed.device.cloud.VmRemoteDevice;
import com.android.tradefed.util.IRunUtil;
import com.android.tradefed.util.RunUtil;
import com.android.tradefed.util.SystemUtil;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Factory to create the different kind of devices that can be monitored by Tf
 */
public class ManagedTestDeviceFactory implements IManagedTestDeviceFactory {

    public static final String NOTIFY_AS_NATIVE = "NOTIFY_AS_NATIVE";
    public static final String IPADDRESS_PATTERN =
            "((^([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\."
                    + "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\."
                    + "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\."
                    + "([01]?\\d\\d?|2[0-4]\\d|25[0-5]))|(localhost)){1}";

    protected boolean mFastbootEnabled;
    protected IDeviceManager mDeviceManager;
    protected IDeviceMonitor mAllocationMonitor;
    protected static final String CHECK_PM_CMD = "ls %s";
    protected static final String EXPECTED_RES = "/system/bin/pm";
    protected static final String EXPECTED_ERROR = "No such file or directory";
    protected static final long FRAMEWORK_CHECK_SLEEP_MS = 500;
    protected static final int FRAMEWORK_CHECK_MAX_RETRY = 3;

    public ManagedTestDeviceFactory(boolean fastbootEnabled, IDeviceManager deviceManager,
            IDeviceMonitor allocationMonitor) {
        mFastbootEnabled = fastbootEnabled;
        mDeviceManager = deviceManager;
        mAllocationMonitor = allocationMonitor;
    }

    @Override
    public IManagedTestDevice createRequestedDevice(IDevice idevice, IDeviceSelection options) {
        IManagedTestDevice testDevice = null;
        if (BaseDeviceType.NATIVE_DEVICE.equals(options.getBaseDeviceTypeRequested())) {
            testDevice =
                    new NativeDevice(
                            idevice,
                            new NativeDeviceStateMonitor(mDeviceManager, idevice, mFastbootEnabled),
                            mAllocationMonitor);
            testDevice.setDeviceState(TestDeviceState.NOT_AVAILABLE);
        } else {
            // Default to-go device is Android full stack device.
            testDevice =
                    new TestDevice(
                            idevice,
                            new DeviceStateMonitor(mDeviceManager, idevice, mFastbootEnabled),
                            mAllocationMonitor);
            testDevice.setDeviceState(TestDeviceState.NOT_AVAILABLE);
        }
        return testDevice;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IManagedTestDevice createDevice(IDevice idevice) {
        IManagedTestDevice testDevice = null;
        if (idevice instanceof VmRemoteDevice) {
            testDevice =
                    new ManagedRemoteDevice(
                            idevice,
                            new DeviceStateMonitor(mDeviceManager, idevice, mFastbootEnabled),
                            mAllocationMonitor);
            testDevice.setDeviceState(TestDeviceState.NOT_AVAILABLE);
        } else if (idevice instanceof RemoteAvdIDevice) {
            testDevice =
                    new RemoteAndroidVirtualDevice(
                            idevice,
                            new DeviceStateMonitor(mDeviceManager, idevice, mFastbootEnabled),
                            mAllocationMonitor);
            testDevice.setDeviceState(TestDeviceState.NOT_AVAILABLE);
        } else if (idevice instanceof StubLocalAndroidVirtualDevice) {
            // Virtual device to be launched by TradeFed locally.
            testDevice =
                    new LocalAndroidVirtualDevice(
                            idevice,
                            new DeviceStateMonitor(mDeviceManager, idevice, mFastbootEnabled),
                            mAllocationMonitor);
        } else if (isTcpDeviceSerial(idevice.getSerialNumber())) {
            if (isRemoteEnvironment()) {
                // If we are in a remote environment, treat the device as such
                testDevice =
                        new NestedRemoteDevice(
                                idevice,
                                new NestedDeviceStateMonitor(
                                        mDeviceManager, idevice, mFastbootEnabled),
                                mAllocationMonitor);
            } else {
                Set<String> nativeSerials = new HashSet<>();
                if (System.getenv(NOTIFY_AS_NATIVE) != null) {
                    nativeSerials.addAll(Arrays.asList(System.getenv(NOTIFY_AS_NATIVE).split(",")));
                }
                if (nativeSerials.contains(idevice.getSerialNumber())) {
                    testDevice =
                            new NativeDevice(
                                    idevice,
                                    new NativeDeviceStateMonitor(
                                            mDeviceManager, idevice, mFastbootEnabled),
                                    mAllocationMonitor);
                } else {
                    // Handle device connected via 'adb connect'
                    testDevice =
                            new RemoteAndroidDevice(
                                    idevice,
                                    new DeviceStateMonitor(
                                            mDeviceManager, idevice, mFastbootEnabled),
                                    mAllocationMonitor);
                    testDevice.setDeviceState(TestDeviceState.NOT_AVAILABLE);
                }
            }
        } else {
            // Default to-go device is Android full stack device.
            testDevice = new TestDevice(idevice,
                    new DeviceStateMonitor(mDeviceManager, idevice, mFastbootEnabled),
                    mAllocationMonitor);
        }

        if (idevice instanceof FastbootDevice) {
            if (((FastbootDevice) idevice).isFastbootD()) {
                testDevice.setDeviceState(TestDeviceState.FASTBOOTD);
            } else {
                testDevice.setDeviceState(TestDeviceState.FASTBOOT);
            }
        } else if (idevice instanceof StubDevice) {
            testDevice.setDeviceState(TestDeviceState.NOT_AVAILABLE);
        }
        testDevice.setFastbootEnabled(mFastbootEnabled);
        testDevice.setFastbootPath(mDeviceManager.getFastbootPath());
        return testDevice;
    }

    /** Return the default {@link IRunUtil} instance. */
    @VisibleForTesting
    protected IRunUtil getRunUtil() {
        return RunUtil.getDefault();
    }

    /**
     * Return true if we are currently running in a remote environment. This will alter the device
     * behavior.
     */
    @VisibleForTesting
    protected boolean isRemoteEnvironment() {
        return SystemUtil.isRemoteEnvironment();
    }

    /** Create a {@link CollectingOutputReceiver}. */
    @VisibleForTesting
    protected CollectingOutputReceiver createOutputReceiver() {
        return new CollectingOutputReceiver();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setFastbootEnabled(boolean enable) {
        mFastbootEnabled = enable;
    }

    /**
     * Helper to device if it's a serial from a remotely connected device. serial format of tcp
     * device is <ip or locahost>:<port>
     */
    public static boolean isTcpDeviceSerial(String serial) {
        final String remotePattern = IPADDRESS_PATTERN + "(:)([0-9]{2,5})(\\b)";
        Pattern pattern = Pattern.compile(remotePattern);
        Matcher match = pattern.matcher(serial.trim());
        if (match.find()) {
            return true;
        }
        return false;
    }
}
