package php.runtime.ext.net;

import php.runtime.Memory;
import php.runtime.annotation.Reflection.*;
import php.runtime.env.Environment;
import php.runtime.ext.NetExtension;
import php.runtime.ext.core.classes.stream.Stream;
import php.runtime.memory.BinaryMemory;
import php.runtime.memory.LongMemory;
import php.runtime.reflection.ClassEntity;

import java.io.*;
import java.net.*;
import java.util.Arrays;

@Name(NetExtension.NAMESPACE + "NetStream")
@Stream.UsePathWithProtocols
public class WrapNetStream extends Stream {
    protected URL url;

    protected Proxy proxy;

    protected boolean eof = false;
    protected int position = 0;

    protected URLConnection urlConnection;

    protected InputStream currentInputStream;
    protected OutputStream currentOutputStream;

    public WrapNetStream(Environment env, URL url) {
        super(env);
        this.url = url;
    }

    public WrapNetStream(Environment env, ClassEntity clazz) {
        super(env, clazz);
    }

    public URLConnection getURLConnection() throws IOException {
        if (urlConnection == null) {
            if (proxy != null) {
                urlConnection = url.openConnection(proxy);
            } else {
                urlConnection = url.openConnection();
            }

            urlConnection.setDoInput(false);
            urlConnection.setDoOutput(false);

            if (getMode().startsWith("r") || getMode().startsWith("w") || getMode().startsWith("a")) {
                urlConnection.setDoInput(true);
            }

            if (getMode().startsWith("w") || getMode().startsWith("a")) {
                urlConnection.setDoOutput(true);
            }
        }

        return urlConnection;
    }

    public HttpURLConnection getHttpURLConnection() throws IOException {
        return (HttpURLConnection) getURLConnection();
    }

    public OutputStream getOutputStream() throws IOException {
        if (currentOutputStream == null) {
            currentOutputStream = getURLConnection().getOutputStream();
        }

        return currentOutputStream;
    }

    public InputStream getInputStream() throws IOException {
        if (currentInputStream == null) {
            URLConnection urlConnection = getURLConnection();
            try {
                currentInputStream = urlConnection.getInputStream();
            } catch (IOException e) {
                if (urlConnection instanceof HttpURLConnection) {
                    currentInputStream = ((HttpURLConnection) urlConnection).getErrorStream();
                } else {
                    throw e;
                }
            }
        }

        return currentInputStream;
    }

    @Override
    @Signature({@Arg("path"), @Arg(value = "mode", optional = @Optional("w+"))})
    public Memory __construct(Environment env, Memory... args) throws IOException {
        super.__construct(env, args);

        if (getMode() == null) {
            setMode("w+");
        }

        url = new URL(args[0].toString());

        return Memory.NULL;
    }

    @Signature
    public WrapURL getURL(Environment env) throws MalformedURLException {
        return new WrapURL(env, new URL(getPath()));
    }

    @Override
    public Memory write(Environment env, Memory... args) throws IOException {
        int len      = args[1].toInteger();
        byte[] bytes = args[0].getBinaryBytes(env.getDefaultCharset());

        len          = len == 0 || len > bytes.length ? bytes.length : len;

        getOutputStream().write(bytes, 0, len);

        return LongMemory.valueOf(len);
    }

    @Override
    public Memory read(Environment env, Memory... args) throws IOException {
        int len = args[0].toInteger();
        if (len <= 0)
            return Memory.FALSE;

        byte[] buf = new byte[len];
        int read;
        read = getInputStream().read(buf);
        eof  = read == -1;

        if (read == -1)
            return Memory.FALSE;

        position += read;
        return new BinaryMemory(Arrays.copyOf(buf, read));
    }

    @Override
    @Signature({
            @Arg(value = "bufferSize", optional = @Optional("4096"))
    })
    public Memory readFully(Environment env, Memory... args) throws IOException {
        final int bufferSize = args[0].toInteger();

        if (bufferSize <= 0) {
            return Memory.FALSE;
        }

        byte[] buff = new byte[bufferSize];
        int len;

        ByteArrayOutputStream tmp = new ByteArrayOutputStream();
        while ((len = getInputStream().read(buff)) > 0) {
            tmp.write(buff, 0, len);

            position += len;
        }

        return new BinaryMemory(tmp.toByteArray());
    }

    @Override
    public Memory eof(Environment env, Memory... args) {
        if (urlConnection == null) {
            return Memory.FALSE;
        }

        return eof ? Memory.TRUE : Memory.FALSE;
    }

    @Override
    public Memory seek(Environment env, Memory... args) throws IOException {
        throw new IOException("Unavailable seek() operation");
    }

    @Override
    public Memory getPosition(Environment env, Memory... args) {
        return LongMemory.valueOf(position);
    }

    @Override
    public Memory close(Environment env, Memory... args) throws IOException {
        if (urlConnection != null && urlConnection instanceof HttpURLConnection) {
            ((HttpURLConnection) urlConnection).disconnect();
        }

        return Memory.NULL;
    }

    @Signature
    public void setProxy(@Nullable Proxy proxy) {
        this.proxy = proxy;
    }

    @Signature
    public Proxy getProxy() {
        return this.proxy;
    }

    @Signature
    public URLConnection getUrlConnection() throws IOException {
        return getURLConnection();
    }

    @Override
    public boolean _isExternalResourceStream() {
        return true;
    }
}

@@ -2074,24 +2074,37 @@
 %0A    %7D%0A%0A    
+synchronized 
 public Input
@@ -2140,32 +2140,32 @@
 s IOException %7B%0A
-
         if (curr
@@ -2330,32 +2330,242 @@
 etInputStream();
+%0A%0A                if (currentInputStream == null && urlConnection instanceof HttpURLConnection) %7B%0A                    currentInputStream = ((HttpURLConnection) urlConnection).getErrorStream();%0A                %7D
 %0A            %7D c
@@ -2812,32 +2812,32 @@
               %7D%0A
-
             %7D%0A  
@@ -2837,32 +2837,178 @@
    %7D%0A        %7D%0A%0A
+        if (currentInputStream == null) %7B%0A            throw new IOException(%22Unable to get input stream for connection %22 + getPath());%0A        %7D%0A%0A
         return c

Fix NPE bug in net streams.