package freenet.client.async;

import freenet.support.CheatingTicker;
import freenet.support.Executor;
import freenet.support.PooledExecutor;
import freenet.support.Ticker;
import freenet.support.WaitableExecutor;
import freenet.support.io.NativeThread;
import junit.framework.TestCase;

public class PersistentJobRunnerImplTest extends TestCase {
    
    final WaitableExecutor exec = new WaitableExecutor(new PooledExecutor());
    final Ticker ticker = new CheatingTicker(exec);
    final JobRunner jobRunner;
    final ClientContext context;
    
    public PersistentJobRunnerImplTest() {
        jobRunner = new JobRunner(exec, ticker, 1000);
        context = new ClientContext(0, null, exec, null, null, null, null, null, null, null, null, ticker, null, null, null, null, null, null, null, null, null, null, null, null, null, null);
        jobRunner.start(context);
        jobRunner.onStarted(false);
        exec.waitForIdle();
        jobRunner.grabHasCheckpointed();
    }
    
    private class WakeableJob implements PersistentJob {
        private boolean wake;
        private boolean started;
        private boolean finished;

        @Override
        public boolean run(ClientContext context) {
            synchronized(this) {
                started = true;
                notifyAll();
                while(!wake) {
                    try {
                        wait();
                    } catch (InterruptedException e) {
                        // Ignore.
                    }
                }
                finished = true;
                notifyAll();
            }
            return false;
        }
        
        public synchronized void wakeUp() {
            wake = true;
            notifyAll();
        }
        
        public synchronized boolean started() {
            return started;
        }
        
        public synchronized boolean finished() {
            return finished;
        }

        public synchronized void waitForStarted() {
            while(!started) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    // Ignore.
                }
            }
        }
        
    }
    
    private class JobRunner extends PersistentJobRunnerImpl {
        
        private boolean hasCheckpointed;

        public JobRunner(Executor executor, Ticker ticker, long interval) {
            super(executor, ticker, interval);
            // TODO Auto-generated constructor stub
        }

        @Override
        public boolean newSalt() {
            // Ignore.
            return false;
        }

        @Override
        protected synchronized void innerCheckpoint(boolean shutdown) {
            hasCheckpointed = true;
            notifyAll();
        }
        
        public synchronized boolean grabHasCheckpointed() {
            boolean ret = hasCheckpointed;
            hasCheckpointed = false;
            return ret;
        }

    }
    
    private class WaitAndCheckpoint implements Runnable {

        private final JobRunner jobRunner;
        private boolean started;
        private boolean finished;
        
        public WaitAndCheckpoint(JobRunner jobRunner2) {
            jobRunner = jobRunner2;
        }

        @Override
        public void run() {
            synchronized(this) {
                started = true;
                notifyAll();
            }
            try {
                jobRunner.waitAndCheckpoint();
            } catch (PersistenceDisabledException e) {
                System.err.println("Impossible: "+e);
                return;
            }
            assertTrue(jobRunner.grabHasCheckpointed());
            synchronized(this) {
                finished = true;
                notifyAll();
            }
        }

        public synchronized boolean hasStarted() {
            return started;
        }
        
        public synchronized void waitForFinished() {
            while(!finished) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    // Ignore.
                }
            }
        }

        public synchronized void waitForStarted() {
            while(!started) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    // Ignore.
                }
            }
        }
        
    }
    
    public void testWaitForCheckpoint() throws PersistenceDisabledException {
        jobRunner.onLoading();
        WakeableJob w = new WakeableJob();
        jobRunner.queue(w, NativeThread.NORM_PRIORITY);
        w.waitForStarted();
        WaitAndCheckpoint checkpointer = new WaitAndCheckpoint(jobRunner);
        new Thread(checkpointer).start();
        checkpointer.waitForStarted();
        w.wakeUp();
        checkpointer.waitForFinished();
        assertTrue(w.finished());
    }
    
    public void testDisabledCheckpointing() throws PersistenceDisabledException {
        jobRunner.setCheckpointASAP();
        exec.waitForIdle();
        assertFalse(jobRunner.mustCheckpoint()); // Has checkpointed, now false.
        jobRunner.disableWrite();
        assertFalse(jobRunner.mustCheckpoint());
        jobRunner.setCheckpointASAP();
        assertFalse(jobRunner.mustCheckpoint());
        
        // Run a job which will request a checkpoint.
        jobRunner.queue(new PersistentJob() {

            @Override
            public boolean run(ClientContext context) {
                return true;
            }
            
        }, NativeThread.NORM_PRIORITY);
        // Wait for the job to complete.
        exec.waitForIdle();
        assertFalse(jobRunner.mustCheckpoint());
    }

}
