/*
 * Copyright (c) 2002, 2007, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

// Parts Copyright (C) 2002-2007 Jeroen Frijters

package sun.nio.ch;

import cli.System.Net.Sockets.Socket;
import cli.System.Net.Sockets.SocketException;
import cli.System.Net.Sockets.AddressFamily;
import cli.System.Net.Sockets.SocketType;
import cli.System.Net.Sockets.ProtocolType;
import cli.System.Net.Sockets.SelectMode;
import cli.System.Collections.ArrayList;
import java.io.IOException;
import java.nio.channels.ClosedSelectorException;
import java.nio.channels.Pipe;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.spi.AbstractSelectableChannel;
import java.nio.channels.spi.AbstractSelector;
import java.nio.channels.spi.SelectorProvider;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

final class DotNetSelectorImpl extends SelectorImpl
{
    private ArrayList channelArray = new ArrayList();
    private long updateCount = 0;

    //Pipe used as a wakeup object.
    private final Pipe wakeupPipe;

    // File descriptors corresponding to source and sink
    private final Socket wakeupSourceFd, wakeupSinkFd;

    // Lock for interrupt triggering and clearing
    private final Object interruptLock = new Object();
    private volatile boolean interruptTriggered = false;

    // class for fdMap entries
    private final static class MapEntry
    {
        SelectionKeyImpl ski;
        long updateCount = 0;
        long clearedCount = 0;
        MapEntry(SelectionKeyImpl ski)
        {
            this.ski = ski;
        }
    }
    private final HashMap<Socket, MapEntry> fdMap = new HashMap<Socket, MapEntry>();

    DotNetSelectorImpl(SelectorProvider sp) throws IOException
    {
        super(sp);
        wakeupPipe = Pipe.open();
        wakeupSourceFd = ((SelChImpl)wakeupPipe.source()).getFD().getSocket();

        // Disable the Nagle algorithm so that the wakeup is more immediate
        SinkChannelImpl sink = (SinkChannelImpl)wakeupPipe.sink();
        (sink.sc).socket().setTcpNoDelay(true);
        wakeupSinkFd = ((SelChImpl)sink).getFD().getSocket();
    }

    protected int doSelect(long timeout) throws IOException
    {
        if (channelArray == null)
            throw new ClosedSelectorException();
        processDeregisterQueue();
        if (interruptTriggered)
        {
            resetWakeupSocket();
            return 0;
        }

        ArrayList read = new ArrayList();
        ArrayList write = new ArrayList();
        ArrayList error = new ArrayList();
        for (int i = 0; i < channelArray.get_Count(); i++)
        {
            SelectionKeyImpl ski = (SelectionKeyImpl)channelArray.get_Item(i);
            int ops = ski.interestOps();
            if (ski.channel() instanceof SocketChannelImpl)
            {
                // TODO there's a race condition here...
                if (((SocketChannelImpl)ski.channel()).isConnected())
                {
                    ops &= SelectionKey.OP_READ | SelectionKey.OP_WRITE;
                }
                else
                {
                    ops &= SelectionKey.OP_CONNECT;
                }
            }
            if ((ops & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0)
            {
                read.Add(ski.getSocket());
            }
            if ((ops & (SelectionKey.OP_WRITE | SelectionKey.OP_CONNECT)) != 0)
            {
                write.Add(ski.getSocket());
            }
            if ((ops & SelectionKey.OP_CONNECT) != 0)
            {
                error.Add(ski.getSocket());
            }
        }
        read.Add(wakeupSourceFd);
        try
        {
            begin();
            int microSeconds = 1000 * (int)Math.min(Integer.MAX_VALUE / 1000, timeout);
            try
            {
                if (false) throw new SocketException();
                // FXBUG docs say that -1 is infinite timeout, but that doesn't appear to work
                Socket.Select(read, write, error, timeout < 0 ? Integer.MAX_VALUE : microSeconds);
            }
            catch (SocketException _)
            {
                read.Clear();
                write.Clear();
                error.Clear();
            }
        }
        finally
        {
            end();
        }
        processDeregisterQueue();
        int updated = updateSelectedKeys(read, write, error);
        // Done with poll(). Set wakeupSocket to nonsignaled  for the next run.
        resetWakeupSocket();
        return updated;
    }

    private int updateSelectedKeys(ArrayList read, ArrayList write, ArrayList error)
    {
        updateCount++;
        int keys = processFDSet(updateCount, read, PollArrayWrapper.POLLIN);
        keys += processFDSet(updateCount, write, PollArrayWrapper.POLLCONN | PollArrayWrapper.POLLOUT);
        keys += processFDSet(updateCount, error, PollArrayWrapper.POLLIN | PollArrayWrapper.POLLCONN | PollArrayWrapper.POLLOUT);
        return keys;
    }

    private int processFDSet(long updateCount, ArrayList sockets, int rOps)
    {
        int numKeysUpdated = 0;
        for (int i = 0; i < sockets.get_Count(); i++)
        {
            Socket desc = (Socket)sockets.get_Item(i);
            if (desc == wakeupSourceFd)
            {
                synchronized (interruptLock)
                {
                    interruptTriggered = true;
                }
                continue;
            }
            MapEntry me = fdMap.get(desc);
            // If me is null, the key was deregistered in the previous
            // processDeregisterQueue.
            if (me == null)
                continue;
            SelectionKeyImpl sk = me.ski;
            if (selectedKeys.contains(sk))
            { // Key in selected set
                if (me.clearedCount != updateCount)
                {
                    if (sk.channel.translateAndSetReadyOps(rOps, sk) &&
                        (me.updateCount != updateCount))
                    {
                        me.updateCount = updateCount;
                        numKeysUpdated++;
                    }
                }
                else
                { // The readyOps have been set; now add
                    if (sk.channel.translateAndUpdateReadyOps(rOps, sk) &&
                        (me.updateCount != updateCount))
                    {
                        me.updateCount = updateCount;
                        numKeysUpdated++;
                    }
                }
                me.clearedCount = updateCount;
            }
            else
            { // Key is not in selected set yet
                if (me.clearedCount != updateCount)
                {
                    sk.channel.translateAndSetReadyOps(rOps, sk);
                    if ((sk.nioReadyOps() & sk.nioInterestOps()) != 0)
                    {
                        selectedKeys.add(sk);
                        me.updateCount = updateCount;
                        numKeysUpdated++;
                    }
                }
                else
                { // The readyOps have been set; now add
                    sk.channel.translateAndUpdateReadyOps(rOps, sk);
                    if ((sk.nioReadyOps() & sk.nioInterestOps()) != 0)
                    {
                        selectedKeys.add(sk);
                        me.updateCount = updateCount;
                        numKeysUpdated++;
                    }
                }
                me.clearedCount = updateCount;
            }
        }
        return numKeysUpdated;
    }

    protected void implClose() throws IOException
    {
        if (channelArray != null)
        {
            // prevent further wakeup
            synchronized (interruptLock) {
                interruptTriggered = true;
            }
            wakeupPipe.sink().close();
            wakeupPipe.source().close();
            for (int i = 0; i < channelArray.get_Count(); i++)
            { // Deregister channels
                SelectionKeyImpl ski = (SelectionKeyImpl)channelArray.get_Item(i);
                deregister(ski);
                SelectableChannel selch = ski.channel();
                if (!selch.isOpen() && !selch.isRegistered())
                    ((SelChImpl)selch).kill();
            }
            selectedKeys = null;
            channelArray = null;
        }
    }

    protected void implRegister(SelectionKeyImpl ski)
    {
        channelArray.Add(ski);
        fdMap.put(ski.getSocket(), new MapEntry(ski));
        keys.add(ski);
    }

    protected void implDereg(SelectionKeyImpl ski) throws IOException
    {
        channelArray.Remove(ski);
        fdMap.remove(ski.getSocket());
        keys.remove(ski);
        selectedKeys.remove(ski);
        deregister(ski);
        SelectableChannel selch = ski.channel();
        if (!selch.isOpen() && !selch.isRegistered())
        {
            ((SelChImpl)selch).kill();
        }
    }

    public Selector wakeup()
    {
        synchronized (interruptLock)
        {
            if (!interruptTriggered)
            {
                setWakeupSocket();
                interruptTriggered = true;
            }
        }
        return this;
    }

    // Sets Windows wakeup socket to a signaled state.
    private void setWakeupSocket() {
        wakeupSinkFd.Send(new byte[1]);
    }

    // Sets Windows wakeup socket to a non-signaled state.
    private void resetWakeupSocket() {
        synchronized (interruptLock)
        {
            if (interruptTriggered == false)
                return;
            resetWakeupSocket0(wakeupSourceFd);
            interruptTriggered = false;
        }
    }

    private static void resetWakeupSocket0(Socket wakeupSourceFd)
    {
        while (wakeupSourceFd.get_Available() > 0)
        {
            wakeupSourceFd.Receive(new byte[1]);
        }
    }
}
