/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.nbandroid.netbeans.gradle.v2.adb.nodes;

import com.android.ddmlib.AndroidDebugBridge;
import com.android.ddmlib.Client;
import com.android.ddmlib.IDevice;
import com.android.ddmlib.NbAndroidAdbHelper;
import com.android.sdklib.SdkVersionInfo;
import java.awt.Image;
import java.beans.PropertyChangeEvent;
import java.io.CharConversionException;
import java.lang.reflect.InvocationTargetException;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
import javax.swing.Action;
import javax.swing.SwingUtilities;
import org.nbandroid.netbeans.gradle.v2.ui.IconProvider;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
import org.openide.nodes.Node.PropertySet;
import org.openide.nodes.NodeEvent;
import org.openide.nodes.NodeListener;
import org.openide.nodes.NodeMemberEvent;
import org.openide.nodes.NodeReorderEvent;
import org.openide.nodes.PropertySupport;
import org.openide.nodes.Sheet;
import org.openide.util.ImageUtilities;
import org.openide.util.NbBundle;
import org.openide.util.Utilities;
import org.openide.util.lookup.Lookups;
import org.openide.xml.XMLUtil;

/**
 *
 * @author tom
 * @author arsi
 */
public class MobileDeviceNode extends AbstractNode implements AndroidDebugBridge.IDeviceChangeListener {

    private static final Logger LOG = Logger.getLogger(MobileDeviceNode.class.getName());
    public static final Map<String, DevicesNode.MobileDeviceHolder> doConnect = new ConcurrentHashMap<>();

    private final DevicesNode.MobileDeviceHolder device;

    MobileDeviceNode(final DevicesNode.MobileDeviceHolder device) {
        super(new DeviceChildren(device.getMasterDevice()), Lookups.fixed(device, device.getMasterDevice()));
        assert device != null;
        this.device = device;
        this.updateDescription();
        this.setIconBaseWithExtension("org/netbeans/modules/android/project/resources/phone.png");
        AndroidDebugBridge.addDeviceChangeListener(this);
        this.addNodeListener(new NodeListener() {

            @Override
            public void childrenAdded(NodeMemberEvent event) {
            }

            @Override
            public void childrenRemoved(NodeMemberEvent event) {
            }

            @Override
            public void childrenReordered(NodeReorderEvent event) {
            }

            @Override
            public void nodeDestroyed(NodeEvent event) {
                AndroidDebugBridge.removeDeviceChangeListener(MobileDeviceNode.this);
            }

            @Override
            public void propertyChange(PropertyChangeEvent event) {
            }
        });
        //after change to tcp mode connect to device
        if (device.getSerialNumber() != null) {
            DevicesNode.MobileDeviceHolder connect = doConnect.remove(device.getSerialNumber());
            if (connect != null) {
                Runnable runnable = new Runnable() {
                    @Override
                    public void run() {
                        NbAndroidAdbHelper.connectEthernet(connect.getUsb().getSerialNumber(), connect.getIp(), 5555);
                    }
                };
                DevicesNode.pool.schedule(runnable, 1, TimeUnit.SECONDS);
            }
        }
    }

    @Override
    public String getHtmlDisplayName() {
        String devName = device.getMasterDevice().getProperty("ro.product.display");
        if (devName == null) {
            devName = device.getMasterDevice().getProperty("ro.product.name");
        }
        try {
            devName = XMLUtil.toElementContent(devName);
        } catch (CharConversionException ex) {
        }
        String androidVersion = SdkVersionInfo.getVersionWithCodename(device.getMasterDevice().getVersion());
        try {
            androidVersion = XMLUtil.toElementContent(androidVersion);
        } catch (CharConversionException ex) {
        }
        String serialNumber = device.getSerialNumber();
        try {
            serialNumber = XMLUtil.toElementContent(serialNumber);
        } catch (CharConversionException ex) {
        }

        return "<html>"
                + "<b>"
                + serialNumber
                + "  "
                + "<font color=\"#0B610B\">"
                + devName
                + "</font>"
                + "</b>"
                + "  "
                + "<i>"
                + androidVersion
                + "</i>"
                + "</html>";
    }

    private void updateDescription() {
        final String serNum = this.device.getSerialNumber();
        final IDevice.DeviceState state = this.device.getMasterDevice().getState();
        this.setShortDescription(NbBundle.getMessage(MobileDeviceNode.class, "HINT_Device",
                serNum, state != null ? state.toString() : "(unknown state)"));
    }

    private Image annotateUsbWifi(Image icon) {
        if (device.hasEthernet()) {
            icon = ImageUtilities.mergeImages(icon, IconProvider.IMG_WIFI_BADGE, 12, 0);
        }
        if (device.hasUSB()) {
            icon = ImageUtilities.mergeImages(icon, IconProvider.IMG_USB_BADGE, 12, 8);
        }
        return icon;
    }

    @Override
    public Image getIcon(int type) {
        return annotateUsbWifi(super.getIcon(type));
    }

    @Override
    public Image getOpenedIcon(int type) {
        return annotateUsbWifi(super.getIcon(type));
    }

    @Override
    public Action[] getActions(boolean context) {
        List<? extends Action> actionsForPath = Utilities.actionsForPath("Android/ADB/MobileDevice");
        return actionsForPath.toArray(new Action[actionsForPath.size()]);
    }

    @Override
    public PropertySet[] getPropertySets() {
        final Sheet.Set defset = Sheet.createPropertiesSet();
        defset.put(new PropertySupport.ReadOnly<String>(
                "PROP_DeviceId",
                String.class,
                NbBundle.getMessage(MobileDeviceNode.class, "PROP_DeviceId"),
                NbBundle.getMessage(MobileDeviceNode.class, "DESC_DeviceId")) {
            @Override
            public String getValue() throws IllegalAccessException, InvocationTargetException {
                return device.getSerialNumber();
            }
        });
        defset.put(new PropertySupport.ReadOnly<String>(
                "PROP_State",
                String.class,
                NbBundle.getMessage(MobileDeviceNode.class, "PROP_State"),
                NbBundle.getMessage(MobileDeviceNode.class, "DESC_State")) {

            @Override
            public String getValue() throws IllegalAccessException, InvocationTargetException {
                return device.getMasterDevice().getState().toString();
            }
        });

        final Sheet.Set devset = new Sheet.Set();
        devset.setExpert(true);
        devset.setName("SET_DeviceProps");
        devset.setDisplayName(NbBundle.getMessage(MobileDeviceNode.class, "SET_DeviceProps"));

        class DeviceProperty extends PropertySupport.ReadOnly<String> {

            private final String name;
            private final String value;

            public DeviceProperty(String name, String value) {
                super(name, String.class, name, name);
                assert name != null;
                assert value != null;
                this.name = name;
                this.value = value;
            }

            @Override
            public String getValue() throws IllegalAccessException, InvocationTargetException {
                return this.value;
            }

        }

        final Map<String, String> props = device.getMasterDevice().getProperties();
        for (Map.Entry<String, String> prop : props.entrySet()) {
            devset.put(new DeviceProperty(prop.getKey(), prop.getValue()));
        }

        return new PropertySet[]{
            defset,
            devset
        };
    }

    public void deviceConnected(IDevice device) {
    }

    public void deviceDisconnected(IDevice device) {
    }

    public void deviceChanged(IDevice device, int changeType) {
        if (this.device.equals(device) && (changeType & IDevice.CHANGE_STATE) == IDevice.CHANGE_STATE) {
            this.updateDescription();
            firePropertySetsChange(null, null);
        }
    }

    private static class DeviceChildren extends Children.Keys<ClientHolder> implements AndroidDebugBridge.IDeviceChangeListener {

        private final IDevice device;

        public DeviceChildren(final IDevice device) {
            assert device != null;
            this.device = device;
        }

        @Override
        protected void addNotify() {
            AndroidDebugBridge.addDeviceChangeListener(this);
            updateKeys();
        }

        private void updateKeys() {
            Set<ClientHolder> keys = new HashSet<ClientHolder>();
            final Client[] clients = device.getClients();
            for (Client client : clients) {
                keys.add(new ClientHolder(client));
            }
            this.setKeys(keys);
        }

        @Override
        protected void removeNotify() {
            AndroidDebugBridge.removeDeviceChangeListener(this);
            this.setKeys(new ClientHolder[0]);
        }

        @Override
        protected Node[] createNodes(ClientHolder key) {
            assert key != null;
            return new Node[]{new ClientNode(key.client)};
        }

        @Override
        public void deviceConnected(IDevice device) {
            //Not important
        }

        @Override
        public void deviceDisconnected(IDevice device) {
            //Not important
        }

        @Override
        public void deviceChanged(IDevice device, int eventType) {
            if (this.device.equals(device) && (eventType & IDevice.CHANGE_CLIENT_LIST) == IDevice.CHANGE_CLIENT_LIST) {
                SwingUtilities.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        updateKeys();
                    }
                });
            }
        }

    }

    private static class ClientHolder {

        public final Client client;

        public ClientHolder(final Client client) {
            assert client != null;
            this.client = client;
        }

        @Override
        public String toString() {
            return this.client.getClientData().getClientDescription();
        }

        @Override
        public int hashCode() {
            return this.client.getClientData().getPid();
        }

        @Override
        public boolean equals(Object obj) {
            if (obj instanceof ClientHolder) {
                final ClientHolder other = (ClientHolder) obj;
                return this.client.getClientData().getPid() == other.client.getClientData().getPid();
            }
            return false;
        }

    }

}