/**
 * Copyright (c) 2005-2012 by Appcelerator, Inc. All Rights Reserved.
 * Licensed under the terms of the Eclipse Public License (EPL).
 * Please see the license.txt included with this distribution for details.
 * Any modifications to this file must keep this entire header intact.
 */
package org.python.pydev.debug.model.remote;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;
import java.net.URLDecoder;
import java.util.Dictionary;
import java.util.Hashtable;

import org.eclipse.core.runtime.IStatus;
import org.python.pydev.core.log.Log;
import org.python.pydev.debug.core.PydevDebugPlugin;
import org.python.pydev.debug.model.AbstractDebugTarget;
import org.python.pydev.debug.model.AbstractDebugTargetWithTransmission;


/**
 * Reads and dispatches commands
 */
public class DebuggerReader implements Runnable {
    /**
     * can be specified to debug this class 
     */
    private static final boolean DEBUG = false;

    /**
     * socket we are reading
     */
    private Socket socket;

    /**
     * Volatile, as multiple threads may ask it to be 'done'
     */
    private volatile boolean done = false;

    /**
     * Lock object for sleeping.
     */
    private Object lock = new Object();

    /**
     * commands waiting for response. Their keys are the sequence ids
     */
    private Dictionary<Integer, AbstractDebuggerCommand> responseQueue = new Hashtable<Integer, AbstractDebuggerCommand>();

    /**
     * we read from this
     */
    private InputStreamReader in;

    /**
     * that's the debugger that made us... we have to finish it when we are done
     */
    private AbstractDebugTarget remote;

    /**
     * Create it
     * 
     * @param s socket we are reading from
     * @param r the debugger associated
     * 
     * @throws IOException
     */
    public DebuggerReader(Socket s, AbstractDebugTargetWithTransmission r) throws IOException {
        remote = (AbstractDebugTarget) r;
        socket = s;
        InputStream sin = socket.getInputStream();
        in = new InputStreamReader(sin);
    }

    /**
     * mark things as done
     */
    public void done() {
        this.done = true;
    }

    /**
     * @param cmd
     */
    public void addToResponseQueue(AbstractDebuggerCommand cmd) {
        int sequence = cmd.getSequence();
        synchronized (responseQueue) {
            responseQueue.put(sequence, cmd);
        }
    }

    /**
     * Parses & dispatches the command
     */
    private void processCommand(String cmdLine) {
        try {
            String[] cmdParsed = cmdLine.split("\t", 3);
            int cmdCode = Integer.parseInt(cmdParsed[0]);
            int seqCode = Integer.parseInt(cmdParsed[1]);
            String payload = URLDecoder.decode(cmdParsed[2], "UTF-8");

            // is there a response waiting
            AbstractDebuggerCommand cmd;
            synchronized (responseQueue) {
                cmd = (AbstractDebuggerCommand) responseQueue.remove(new Integer(seqCode));
            }

            if (cmd == null) {
                if (remote != null) {
                    remote.processCommand(cmdParsed[0], cmdParsed[1], payload);
                } else {
                    PydevDebugPlugin.log(IStatus.ERROR, "internal error, command received no target", null);
                }
            } else {
                cmd.processResponse(cmdCode, payload);
            }
        } catch (Exception e) {
            Log.log(e);
            throw new RuntimeException(e);
        }
    }

    /**
     * keep reading until we finish (that should happen when an exception is thrown, or if it is set as
     * done from outside)
     * 
     * @see java.lang.Runnable#run()
     */
    @Override
    public void run() {
        while (!done) {
            try {
                String cmdLine = readLine();
                if (DEBUG) {
                    System.err.println("receive cmd: " + cmdLine);
                }
                if (cmdLine != null && cmdLine.trim().length() > 0) {
                    processCommand(cmdLine);
                }
                synchronized (lock) {
                    Thread.sleep(50);
                }
            } catch (Exception e1) {
                done = true;
                //that's ok, it means that the client finished
                if (DEBUG) {
                    e1.printStackTrace();
                }
            }

            if (done || socket == null || !socket.isConnected()) {
                AbstractDebugTarget target = remote;

                if (target != null) {
                    target.terminate();
                }
                done = true;
            }
        }
    }

    /**
     * Implemented our own: with the BufferedReader, when the socket was closed, it still appeared stuck in the method.
     * 
     * @return a line that was read from the debugger.
     * @throws IOException
     */
    private String readLine() throws IOException {
        StringBuffer contents = new StringBuffer();
        int i;
        while ((i = in.read()) != -1) {
            char c = (char) i;
            if (c == '\n' || c == '\r') {
                return contents.toString();
            }
            contents.append(c);
        }
        throw new IOException("Done");
    }
}
