package se.vidstige.jadb;

import se.vidstige.jadb.managers.Bash;

import java.io.*;
import java.util.ArrayList;
import java.util.List;

public class JadbDevice {
    @SuppressWarnings("squid:S00115")
    public enum State {
        Unknown,
        Offline,
        Device,
        Recovery,
        BootLoader
    }

    private final String serial;
    private final ITransportFactory transportFactory;

    JadbDevice(String serial, String type, ITransportFactory tFactory) {
        this.serial = serial;
        this.transportFactory = tFactory;
    }

    static JadbDevice createAny(JadbConnection connection) {
        return new JadbDevice(connection);
    }

    private JadbDevice(ITransportFactory tFactory) {
        serial = null;
        this.transportFactory = tFactory;
    }

    private State convertState(String type) {
        switch (type) {
            case "device":     return State.Device;
            case "offline":    return State.Offline;
            case "bootloader": return State.BootLoader;
            case "recovery":   return State.Recovery;
            default:           return State.Unknown;
        }
    }

    private Transport getTransport() throws IOException, JadbException {
        Transport transport = transportFactory.createTransport();
        if (serial == null) {
            send(transport,"host:transport-any");
        } else {
            send(transport,"host:transport:" + serial);
        }
        return transport;
    }

    public String getSerial() {
        return serial;
    }

    public State getState() throws IOException, JadbException {
        Transport transport = transportFactory.createTransport();
        if (serial == null) {
            send(transport, "host:get-state");
        } else {
            send(transport, "host-serial:" + serial + ":get-state");
        }

        State state = convertState(transport.readString());
        transport.close();
        return state;
    }

    /** <p>Execute a shell command.</p>
     *
     * <p>For Lollipop and later see: {@link #execute(String, String...)}</p>
     *
     * @param command main command to run. E.g. "ls"
     * @param args arguments to the command.
     * @return combined stdout/stderr stream.
     * @throws IOException
     * @throws JadbException
     */
    public InputStream executeShell(String command, String... args) throws IOException, JadbException {
        Transport transport = getTransport();
        StringBuilder shellLine = buildCmdLine(command, args);
        send(transport, "shell:" + shellLine.toString());
        return new AdbFilterInputStream(new BufferedInputStream(transport.getInputStream()));
    }

    /**
     *
     * @deprecated Use InputStream executeShell(String command, String... args) method instead. Together with
     * Stream.copy(in, out), it is possible to achieve the same effect.
     */
    @Deprecated
    public void executeShell(OutputStream output, String command, String... args) throws IOException, JadbException {
        Transport transport = getTransport();
        StringBuilder shellLine = buildCmdLine(command, args);
        send(transport, "shell:" + shellLine.toString());
        if (output != null) {
        	AdbFilterOutputStream out = new AdbFilterOutputStream(output);
        	try {
        		transport.readResponseTo(out);
        	} finally {
        		out.close();
        	}
        }
    }

    /** <p>Execute a command with raw binary output.</p>
     *
     * <p>Support for this command was added in Lollipop (Android 5.0), and is the recommended way to transmit binary
     * data with that version or later. For earlier versions of Android, use
     * {@link #executeShell(String, String...)}.</p>
     *
     * @param command main command to run, e.g. "screencap"
     * @param args arguments to the command, e.g. "-p".
     * @return combined stdout/stderr stream.
     * @throws IOException
     * @throws JadbException
     */
    public InputStream execute(String command, String... args) throws IOException, JadbException {
        Transport transport = getTransport();
        StringBuilder shellLine = buildCmdLine(command, args);
        send(transport, "exec:" + shellLine.toString());
        return new BufferedInputStream(transport.getInputStream());
    }

    /**
     * Builds a command line string from the command and its arguments.
     *
     * @param command the command.
     * @param args the list of arguments.
     * @return the command line.
     */
    private StringBuilder buildCmdLine(String command, String... args) {
        StringBuilder shellLine = new StringBuilder(command);
        for (String arg : args) {
            shellLine.append(" ");
            shellLine.append(Bash.quote(arg));
        }
        return shellLine;
    }

    public List<RemoteFile> list(String remotePath) throws IOException, JadbException {
        Transport transport = getTransport();
        SyncTransport sync = transport.startSync();
        sync.send("LIST", remotePath);

        List<RemoteFile> result = new ArrayList<>();
        for (RemoteFileRecord dent = sync.readDirectoryEntry(); dent != RemoteFileRecord.DONE; dent = sync.readDirectoryEntry()) {
            result.add(dent);
        }
        return result;
    }

    private int getMode(File file) {
        //noinspection OctalInteger
        return 0664;
    }

    public void push(InputStream source, long lastModified, int mode, RemoteFile remote) throws IOException, JadbException {
        Transport transport = getTransport();
        SyncTransport sync = transport.startSync();
        sync.send("SEND", remote.getPath() + "," + Integer.toString(mode));

        sync.sendStream(source);

        sync.sendStatus("DONE", (int) lastModified);
        sync.verifyStatus();
    }

    public void push(File local, RemoteFile remote) throws IOException, JadbException {
        FileInputStream fileStream = new FileInputStream(local);
        push(fileStream, local.lastModified(), getMode(local), remote);
        fileStream.close();
    }

    public void pull(RemoteFile remote, OutputStream destination) throws IOException, JadbException {
        Transport transport = getTransport();
        SyncTransport sync = transport.startSync();
        sync.send("RECV", remote.getPath());

        sync.readChunksTo(destination);
    }

    public void pull(RemoteFile remote, File local) throws IOException, JadbException {
        FileOutputStream fileStream = new FileOutputStream(local);
        pull(remote, fileStream);
        fileStream.close();
    }

    private void send(Transport transport, String command) throws IOException, JadbException {
        transport.send(command);
        transport.verifyResponse();
    }

    @Override
    public String toString() {
        return "Android Device with serial " + serial;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((serial == null) ? 0 : serial.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        JadbDevice other = (JadbDevice) obj;
        if (serial == null) {
            if (other.serial != null)
                return false;
        } else if (!serial.equals(other.serial))
            return false;
        return true;
    }
}

@@ -1294,124 +1294,64 @@
     
-if (serial == null) %7B%0A            send(transport,%22host:transport-any%22);%0A        %7D else %7B%0A            send(transport,
+send(transport, serial == null ? %22host:transport-any%22 : 
 %22hos
@@ -1372,29 +1372,20 @@
 + serial
+ 
 );%0A
-        %7D%0A
         
@@ -1613,121 +1613,59 @@
     
-if (serial == null) %7B%0A            send(transport, %22host:get-state%22);%0A        %7D else %7B%0A            send(transport,
+send(transport, serial == null ? %22host:get-state%22 :
  %22ho
@@ -1701,26 +1701,16 @@
 state%22);
-%0A        %7D
 %0A%0A      

Convert "if"s to ternary operators