/*
 This file is part of GNUnet.
 (C) 2011, 2012 Christian Grothoff (and other contributing authors)

 GNUnet is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published
 by the Free Software Foundation; either version 3, or (at your
 option) any later version.

 GNUnet 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 for more details.

 You should have received a copy of the GNU General Public License
 along with GNUnet; see the file COPYING.  If not, write to the
 Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 Boston, MA 02111-1307, USA.
 */

package org.gnunet.util;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.Pipe;
import java.nio.channels.SelectionKey;
import java.util.LinkedList;

/**
 * Server the entry point class for every gnunet-java component providing services
 * to other components.
 *
 * The configuration for the server (i.e. ports/interfaces) is loaded with the standard configuration system.
 *
 * Note that other processes can send signals to the service via a pipe, whose name has to be given in the
 * environment variable GNUNET_OS_CONTROL_PIPE
 */
public abstract class Service extends Program {
    private static final Logger logger = LoggerFactory
            .getLogger(Service.class);

    private Server s;
    private String serviceName;
    private RelativeTime idleTimeout;
    private boolean requireFound;

    private Pipe.SourceChannel sigpipeChannel;

    public Service(String serviceName, RelativeTime idleTimeout, boolean requireFound) {
        this.serviceName = serviceName;
        this.idleTimeout = idleTimeout;
        this.requireFound = requireFound;
    }

    /**
     * Obtain the server used by a service.  Note that the server must NOT
     * be destroyed by the caller.
     *
     * @return handle to the server for this service, NULL if there is none
     */
    public final Server getServer() {
        return s;
    }

    /**
     * Stop the service.
     */
    public void stop() {
        s.stopListening();
    }

    public void runHook() {
        String ip4AddrList = getConfiguration().getValueString(serviceName, "ACCEPT_FROM").orNull();
        String ip6AddrList = getConfiguration().getValueString(serviceName, "ACCEPT_FROM6").orNull();
        int port = getConfiguration().getValueNumber(serviceName, "PORT").get().intValue();

        LinkedList<SocketAddress> addrs = new LinkedList<SocketAddress>();

        if (ip4AddrList != null) {
            for (String ip4Addr : ip4AddrList.split("[;]")) {
                InetAddress addr = Resolver.getInetAddressFromString(ip4Addr);
                addrs.add(new InetSocketAddress(addr, port));
            }
        }

        if (ip6AddrList != null) {
            for (String ip6Addr : ip6AddrList.split("[;]")) {
                InetAddress addr = Resolver.getInetAddressFromString(ip6Addr);
                addrs.add(new InetSocketAddress(addr, port));
            }
        }

        s = new Server(addrs, idleTimeout, requireFound);

        String pipeName = System.getenv("GNUNET_OS_CONTROL_PIPE");
        if (pipeName != null && !pipeName.isEmpty()) {
            Scheduler.FilePipe p = Scheduler.openFilePipe(new File(pipeName));

            Scheduler.TaskConfiguration t = new Scheduler.TaskConfiguration(RelativeTime.FOREVER,
                    new SigpipeTask());
            t.addSelectEvent(p.getSource(), SelectionKey.OP_READ);
            t.setLifeness(false);
            t.schedule();
            sigpipeChannel = p.getSource();
        }

        run();
    }

    private class SigpipeTask implements Scheduler.Task {
        @Override
        public void run(Scheduler.RunContext ctx) {
            ByteBuffer b = ByteBuffer.allocate(1);
            int n;
            try {
                n = sigpipeChannel.read(b);
            } catch (IOException e) {
                logger.error("error reading signal pipe", e);
                return;
            }
            b.flip();
            boolean stopped = false;

            if (n == 1) {
                byte sig = b.get();
                // 15=sigterm
                if (sig == 15) {
                    logger.info("service shutting down");
                    getServer().stopListening();
                    stopped = true;
                }
            }
            if (!stopped) {
                Scheduler.TaskConfiguration t = new Scheduler.TaskConfiguration(RelativeTime.FOREVER, this);
                t.schedule();
            } else {
                try {
                    sigpipeChannel.close();
                } catch (IOException e) {
                    logger.error("could not close sigpipe channel, quitting");
                }
                System.exit(2);
            }
        }
    }
}