/*
 * Acquisition
 * http://www.xlife.org/
 *
 * Copyright (c) 2002-2003 David Keiichi Watanabe
 * davew@xlife.org
 *
 * This program 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 2
 * of the License, or (at your option) any later version.
 * 
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

/* LimeWire */
import com.limegroup.gnutella.*;
import com.limegroup.gnutella.browser.*;
import com.limegroup.gnutella.downloader.*;
import com.limegroup.gnutella.messages.*;
import com.limegroup.gnutella.settings.*;
import com.limegroup.gnutella.util.*;

/* Java */
import java.io.*;
import java.net.*;
import java.util.*;


public class AqMain
{

/* Instances */
    
    public static Thread cThread;
    public static Thread tThread;
    public static Thread dThread;
    public static Thread gcThread;
    public static Thread dispatchThread;
    
    public static boolean coreHasStarted = false;

/* Main */

    public static void main(String[] args) 
    {
        (new Thread(new ReaderThread())).start();
        System.err.println(CommonUtils.getVendor());

        /* create app support directory */
        
            if (!CommonUtils.getUserSettingsDir().exists()) {
                CommonUtils.getUserSettingsDir().mkdirs();
            }
            
        /* start LW */
        
            (new RouterService(new AqEventHandler())).start();

        /* launch threads */

            gcThread = new Thread(new GCThread());
            cThread = new Thread(new ConnectionUpdate());
            tThread = new Thread(new TransferUpdate());
            dThread = new Thread(new DownloadRetry());
            dispatchThread = new Thread(new DispatchThread());
            
            cThread.setDaemon(true);
            tThread.setDaemon(true);
            dThread.setDaemon(true);
            gcThread.setDaemon(true);
            dispatchThread.setDaemon(true);

            cThread.start();
            tThread.start();
            dThread.start();
            gcThread.start();
            dispatchThread.start();
            
            coreHasStarted = true;
    }
    
    public static void cleanIncompleteDirectory() throws Exception {
        File incompleteDir = SharingSettings.INCOMPLETE_DIRECTORY.getValue();
        String[] files = incompleteDir.list();

        if (files != null) {
            for (int i=0; i<files.length; i++) {
                File f = new File(incompleteDir, files[i]);
                if (files[i].startsWith(IncompleteFileManager.PREVIEW_PREFIX)) {
                    f.delete();
                } else if (f.length() == 0) {
                    f.delete();
                }
            }
        }
    }

    public static void shutdown() {
        try {
            System.err.println("AqMain: shutdown() A");
            cleanIncompleteDirectory();
            Statistics.instance().shutdown();
            RouterService.getHostCatcher().write();		/* network.cache */
            SettingsHandler.save();                             /* limewire.props */
            //UrnCache.instance().persistCache();               /* fileurns.cache */
            //RouterService.getDownloadManager().writeSnapshot();	/* downloads.cache */
            System.err.println("AqMain: shutdown() B");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
    

/* System.in */

/* Incoming Pipe */

    /* 
        The following is a listening thread which receives control instructions from the primary Acquisition process.  When the pipe is broken (the parent process has exited), we calmly clean up everything and exit.
    */

    class ReaderThread implements Runnable 
    {
        public static Vector dispatches = new Vector();

        public void run() 
        {
            try 
            {
                Vector v = new Vector();
                StringBuffer fragment = new StringBuffer();
                int b;
            
                System.err.println("ReaderThread: Listening to System.in");
                
                /* read loop */
    
                    InputStreamReader reader = new InputStreamReader(System.in, "UTF-8");
                    while ((b = reader.read()) > -1)
                    {
                        char character = (char)b;
                    
                        if (character == '|' || character == '\n') {
                            v.add(fragment.toString());
                            fragment = new StringBuffer();
        
                            if (character == '\n') {
                                synchronized (ReaderThread.dispatches) {
                                    dispatches.add(v);
                                }
                                v = new Vector();
                            }
                        } else {
                            fragment.append(character);
                        }
                    }
                
                /* shutdown */
                
                    System.err.println("ReaderThread: System.in EOF");
                    
                    /* kill thread that will terminate the JVM in 3 seconds, *regardless* of what happens next */
                    
                    (new Thread() {
                        public void run() {
                            try {
                                Thread.sleep(4000); /* 4 seconds */
                            } catch (Exception e) {}
                            System.err.println("ReaderThreadTerminator: killing");
                            System.exit(0);
                        }
                    }).start();
                    
                    AqEvent.shouldSignalEvents = false;
                    if (AqMain.cThread != null) AqMain.cThread.interrupt();
                    if (AqMain.tThread != null) AqMain.tThread.interrupt();
                    if (AqMain.dThread != null) AqMain.dThread.interrupt();
                    if (AqMain.gcThread != null) AqMain.gcThread.interrupt();
                    if (AqMain.dispatchThread != null) AqMain.dispatchThread.interrupt();
    
                    ConnectionSettings.FORCE_IP_ADDRESS.revertToDefault();
                    ConnectionSettings.FORCED_IP_ADDRESS_STRING.revertToDefault();
    
                    AqMain.shutdown();
                    System.exit(0);
                
            } catch (Exception e) {
                e.printStackTrace();
                System.exit(0);
            }
        }
    }


/* Dispatch */

    class DispatchThread implements Runnable {
        public void run() {
            while (true) {
                if (AqMain.coreHasStarted) 
                {
                    Vector dispatches = new Vector();

                    synchronized (ReaderThread.dispatches) {
                        dispatches.addAll(ReaderThread.dispatches);
                        ReaderThread.dispatches.removeAllElements();
                    }
                    
                    for (int i=0; i<dispatches.size(); i++) {
                        try {
                            AqDispatcher.dispatchCommand((Vector)dispatches.get(i));
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
                try {
                    Thread.sleep(300); /* 0.3 second */
                } catch (Exception e) {
                    break;
                }
            }
        }
    }


/* ConnectionUpdate */

    class ConnectionUpdate implements Runnable {
        public void run() {
            while (true) {

                ConnectionManager cm = RouterService.getConnectionManager();
                Iterator i;
                    
                try {

                    i = cm.getInitializedConnections().iterator();
                    while (i.hasNext()) publishUpdate((Connection)i.next());
    
                    i = cm.getInitializedClientConnections().iterator();
                    while (i.hasNext()) publishUpdate((Connection)i.next());
    
                    AqEvent.signalEvent(AqEvent.kLWEventConnectionsUpdated, null);
                    
                    /* Sleep */

                        int total = cm.getNumInitializedConnections() + cm.getNumInitializedClientConnections();
                        try {
                            if (total > 10)
                                Thread.sleep(5000);  /* 5 seconds */
                            else
                                Thread.sleep(2000);  /* 2 seconds */
                        } catch (InterruptedException e) {
                            break;
                        }

                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        
        public void publishUpdate(Connection c) {
            AqEvent.signalEvent(AqEvent.kLWEventUpdateConnectionStats, c);
        }
    }


/* TransferUpdate */

    class TransferUpdate implements Runnable {
        public void run() {

            DownloadManager dm = RouterService.getDownloadManager();
            UploadManager um = RouterService.getUploadManager();

            while (true) {
                try {
                    List waiting = dm.getWaitingDownloads();
                    List active = dm.getActiveDownloads();
                    List uploads = um.getUploads();
                    
                    Iterator i;
                    
                    /* Downloads */
        
                        i = active.iterator();
                        while (i.hasNext()) publishUpdate((Downloader)i.next());
        
                        i = waiting.iterator();
                        while (i.hasNext()) publishUpdate((Downloader)i.next());
        
                        AqEvent.signalEvent(AqEvent.kLWEventDownloadsUpdated, null);
                        
                    /* Uploads */
            
                        i = uploads.iterator();
                        while (i.hasNext()) publishUpdate((Uploader)i.next());
    
                        AqEvent.signalEvent(AqEvent.kLWEventUploadsUpdated, null);
                        
                    /* Sleep */
                    
                        int totalTransfers = waiting.size() + active.size() + uploads.size();
                        
                        try {
                            if (totalTransfers > 30)
                                Thread.sleep(1000);  /* 1 seconds */
                            else
                                Thread.sleep(1000);  /* 1 second */
                        } catch (InterruptedException e) {
                            break;
                        }
 
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        
        public void publishUpdate(Downloader d) {
            AqEvent.signalEvent(AqEvent.kLWEventUpdateDownloadStats, d);
        }

        public void publishUpdate(Uploader u) {
            AqEvent.signalEvent(AqEvent.kLWEventUpdateUploadStats, u);
        }
    }
    

/* DownloadRetry */

    class DownloadRetry implements Runnable {
        public void run() {
            while (true) {
                try {

                    /* Sleep */

                        try {
                            Thread.sleep(60*60*1000);  /* 60 minutes */
                        } catch (InterruptedException e) {
                            break;
                        }
                    
                    /* Downloads */

                        List waiting = RouterService.getDownloadManager().getWaitingDownloads();
                        Downloader d;
                        Iterator i;
        
                        i = waiting.iterator();
                        while (i.hasNext()) {
                            d = (Downloader)i.next();
                            d.resume();
                        }
                                                                
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

/* Garbage Collection */

    class GCThread implements Runnable {
        public void run() {
            while (true) {
                try {
                    Thread.sleep(120 * 1000);  /* 120 seconds */
                    System.gc();
                } catch (Exception e) {
                    break;
                }
            }
        }
    }