/*
 *  PHEX - The pure-java Gnutella-servent.
 *  Copyright (C) 2001 - 2008 Phex Development Group
 *
 *  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
 * 
 *  --- SVN Information ---
 *  $Id: DownloadDataWriter.java 4362 2009-01-16 10:27:18Z gregork $
 */
package phex.download;

import java.util.List;
import java.util.ListIterator;

import org.apache.commons.lang.time.DateUtils;

import phex.common.ThreadTracking;
import phex.common.log.NLogger;
import phex.download.swarming.SWDownloadFile;
import phex.download.swarming.SwarmingManager;
import phex.prefs.core.DownloadPrefs;

/**
 * Extra thread that is responsible to write buffered download data to
 * disk regulary.
 */
public class DownloadDataWriter implements Runnable
{
    private Thread thread;
    private boolean isShutingDown;
    private long lastCompleteWrite;
    private boolean isWriteCycleRequested;
    private SwarmingManager swarmingMgr;
    
    public DownloadDataWriter( SwarmingManager downloadService )
    {
        swarmingMgr = downloadService;
    }
    
    public void start()
    {
        isShutingDown = false;
        thread = new Thread( ThreadTracking.rootThreadGroup, this,
            "DownloadDataWriter" );
        thread.setDaemon( true );
        thread.start();
    }
    
    /**
     * This call performs a shutdown of the download data writer thread.
     * The call blocks until the DownloadDataWriter thread died.
     */
    public void shutdown()
    {
        isShutingDown = true;
        // trigger thread cycle
        triggerWriteCycle();
        try
        {
            thread.join();
        }
        catch ( InterruptedException exp )
        {
            NLogger.error( DownloadDataWriter.class, exp, exp );
            Thread.currentThread().interrupt();
        }
        
        // perform a last write operation to ensure all data is on disk
        writeDownloadData();
    }

    public void run()
    {
        while ( !isShutingDown )
        {
            try
            {
                do
                {
                    writeDownloadData();
                }
                while ( isWriteCycleRequested );
                // loop around write cycles as long as data is filling...
                // this is necessary for very fast downloads..
                waitForNotify();
            }
            catch (Throwable th)
            {
                NLogger.error( DownloadDataWriter.class, th, th );
            }
        }
    }
    
    private synchronized void waitForNotify()
    {
        NLogger.debug(DownloadDataWriter.class, "Waiting..." );
        try
        {
            wait( 5000 );
        }
        catch (InterruptedException exp)
        {
            NLogger.error( DownloadDataWriter.class, exp, exp );
        }
        NLogger.debug(DownloadDataWriter.class, "Woke..." );
    }
    
    public synchronized void triggerWriteCycle()
    {
        NLogger.debug(DownloadDataWriter.class, "Triggering write cycle." );
        isWriteCycleRequested = true;
        notifyAll();
    }
    
    private void writeDownloadData()
    {
        if ( !swarmingMgr.isDownloadActive() && !isWriteCycleRequested )
        {
            return;
        }
        
        long bufferedDataWritten = 0;
        long totalBufferedSize = 0;
        boolean performCompleteWrite = false;
        if ( isShutingDown || isWriteCycleRequested || 
             lastCompleteWrite + DateUtils.MILLIS_PER_MINUTE < System.currentTimeMillis() )
        {
            NLogger.debug(DownloadDataWriter.class, "Time for complete write cycle." );
            isWriteCycleRequested = false;
            performCompleteWrite = true;
        }
        
        // write limit is 90% of configured max.
        int maxPerDownloadBuffer = DownloadPrefs.MaxWriteBufferPerDownload.get().intValue();
        maxPerDownloadBuffer = (int)(maxPerDownloadBuffer * 0.9);
        
        List<SWDownloadFile> downloadList = swarmingMgr.getDownloadFileListCopy();
        ListIterator<SWDownloadFile> iterator = downloadList.listIterator();
        while( iterator.hasNext() )
        {
            SWDownloadFile downloadFile = iterator.next();
            MemoryFile memoryFile = downloadFile.getMemoryFile();
            long bufferedSize = memoryFile.getBufferedDataLength();
            totalBufferedSize += bufferedSize;
            
            if ( performCompleteWrite || memoryFile.isBufferWritingRequested() ||
                 bufferedSize >= maxPerDownloadBuffer )
            {
                NLogger.debug(DownloadDataWriter.class,
                    "Trigger buffer write for " + downloadFile + 
                    ", amount: " + bufferedSize );
                memoryFile.writeBuffersToDisk();
                bufferedDataWritten += bufferedSize;
                // remove from buffer since not needed anymore in possible
                // following complete write cycle
                iterator.remove();
            }
        }
        
        NLogger.debug(DownloadDataWriter.class,
            "Total buffered data was: " + totalBufferedSize );
        
        // write limit is 90% of configured max.
        int maxTotalBuffer = DownloadPrefs.MaxTotalDownloadWriteBuffer.get().intValue();
        maxTotalBuffer = (int)(maxTotalBuffer * 0.9);
        
        // if we have not already written everything but have a high total buffer
        // size, we write down the complete remaining download buffers to disk.
        if ( !performCompleteWrite && totalBufferedSize >= maxTotalBuffer )
        {
            performCompleteWrite = true;
            iterator = downloadList.listIterator();
            while( iterator.hasNext() )
            {
                SWDownloadFile downloadFile = iterator.next();
                MemoryFile memoryFile = downloadFile.getMemoryFile();
                long bufferedSize = memoryFile.getBufferedDataLength();
                NLogger.debug(DownloadDataWriter.class,
                    "Trigger buffer write for " + downloadFile + 
                    ", amount: " + bufferedSize );
                memoryFile.writeBuffersToDisk();
                bufferedDataWritten += bufferedSize;
            }
        }
        if ( performCompleteWrite )
        {
            lastCompleteWrite = System.currentTimeMillis();
        }
        if ( bufferedDataWritten > 0 )
        {
            swarmingMgr.notifyDownloadListChange();
        }
    }
}