/*
    JPC: A x86 PC Hardware Emulator for a pure Java Virtual Machine
    Release Version 2.0

    A project from the Physics Dept, The University of Oxford

    Copyright (C) 2007 Isis Innovation Limited

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License version 2 as published by
    the Free Software Foundation.

    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.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 
    Details (including contact information) can be found at: 

    www.physics.ox.ac.uk/jpc
*/

package org.jpc.emulator.memory.codeblock;

import java.util.*;

import org.jpc.emulator.memory.codeblock.optimised.*;
import org.jpc.emulator.memory.codeblock.bytecodecompiler.*;
import org.jpc.emulator.memory.*;
import org.jpc.emulator.processor.*;

public class BackgroundCompiler implements CodeBlockCompiler
{
    private CodeBlockCompiler immediate, delayed, backup;
    private ExecuteCountingCodeBlockWrapper[] pendingBlocks;
    private int queueLength;
    private CompilerThread compilerThread;

    public BackgroundCompiler(CodeBlockCompiler immediate, CodeBlockCompiler delayed)
    {
        this.immediate = immediate;
        this.delayed = delayed;
        queueLength = 0;
        pendingBlocks = new ExecuteCountingCodeBlockWrapper[1024];

        compilerThread = new CompilerThread();
        //backup = new org.jpc.sourcecompiler.JavaSourceCompiler();
    }

    class CompilerThread extends Thread
    {
        CompilerThread()
        {
            super("Background CodeBlock Compiler");
            start();
            setPriority(Math.max(Thread.MIN_PRIORITY, Thread.currentThread().getPriority()-1));
        }

        public void run()
        {
            while (true)
            {
                try
                {
                    ExecuteCountingCodeBlockWrapper target = getNextBlockForCompilation(200);
                    if (target == null)
                    {
                        Thread.sleep(100);
                        continue;
                    }

                    RealModeUBlock src = (RealModeUBlock) target.getBlock();
		    RealModeCodeBlock result = null;

//                     long start = System.nanoTime();
//                     String compilerID = "<<None>>";
//                     try
//                     {
//                         compilerID = "ByteCode";
                    result = delayed.getRealModeCodeBlock(src.getAsInstructionSource());
//                     }
//                     catch (Exception e)
//                     {
//                         compilerID = "JavaSource";
//                         result = backup.getRealModeCodeBlock(src.getAsInstructionSource());
//                     }
                    
                    target.replaceInOwner(result);

                    //double tt = (System.nanoTime() - start)*1e-6;
                    //System.err.printf("Block length %d compiled by "+compilerID+" after %d executes: Time %.2f\n", result.getX86Count(), target.executeCount, tt);
                }
                catch (Exception e)
                {
//                     System.out.println("Exception in compiler thread: " + e);
                }
		catch (ClassFormatError e) 
                {
		    System.out.println(e);
		}
            }
        }
    }

    private synchronized ExecuteCountingCodeBlockWrapper getNextBlockForCompilation(int threshold)
    {
        int maxCount = 0;
        int maxIndex = -1;
        ExecuteCountingCodeBlockWrapper result = null;

        for (int i=0; i<queueLength; i++)
        {
            ExecuteCountingCodeBlockWrapper w = pendingBlocks[i];
            if (w == null)
                continue;

            if (w.executeCount > maxCount)
            {
                maxCount = w.executeCount;
                result = w;
                maxIndex = i;
            }
        }

        if ((maxIndex < 0) || (maxCount < threshold))
            return null;

        pendingBlocks[maxIndex] = null;
        return result;
    }

    private synchronized void compactQueue(ExecuteCountingCodeBlockWrapper current)
    {
        queueLength = 0;
        for (int i=0; i<pendingBlocks.length; i++)
            if (pendingBlocks[i] != null)
            {
                pendingBlocks[queueLength++] = pendingBlocks[i];
                pendingBlocks[i] = null;
            }

        if (queueLength == pendingBlocks.length)
        {
            ExecuteCountingCodeBlockWrapper[] buffer = new ExecuteCountingCodeBlockWrapper[pendingBlocks.length * 2];
            System.arraycopy(pendingBlocks, 0, buffer, 0, pendingBlocks.length);
            pendingBlocks = buffer;
            pendingBlocks[queueLength] = current;
            queueLength++;
        }
    }

    public RealModeCodeBlock getRealModeCodeBlock(InstructionSource source)
    {
        RealModeCodeBlock imm = immediate.getRealModeCodeBlock(source);
	return new RealModeCodeBlockWrapper(imm);
    }

    public ProtectedModeCodeBlock getProtectedModeCodeBlock(InstructionSource source)
    {
        return immediate.getProtectedModeCodeBlock(source);
    }

    final class ExecuteCountingCodeBlockWrapper extends AbstractCodeBlockWrapper implements RealModeCodeBlock
    {
        int executeCount, diff;
        RealModeCodeBlockWrapper owner;

	public ExecuteCountingCodeBlockWrapper(RealModeCodeBlock block)
	{
            super(block);
            diff = block.getX86Count();
        }

        void enqueue(RealModeCodeBlockWrapper owner)
        {
            this.owner = owner;
            
            try
            {
                pendingBlocks[queueLength] = this;
                queueLength++;
            }
            catch (ArrayIndexOutOfBoundsException e)
            {
                compactQueue(this);
            }
        }

        public int execute(Processor cpu)
        {
            executeCount += diff;
            return super.execute(cpu);
        }

        void replaceInOwner(RealModeCodeBlock replacement)
        {
            owner.setBlock(new ReplacementBlockTrigger(replacement));
        }
    }

    class RealModeCodeBlockWrapper extends AbstractCodeBlockWrapper implements RealModeCodeBlock
    {
	public RealModeCodeBlockWrapper(RealModeCodeBlock block)
	{
            super(new ExecuteCountingCodeBlockWrapper(block));
            ExecuteCountingCodeBlockWrapper w = (ExecuteCountingCodeBlockWrapper) getBlock();
            w.enqueue(this);
	}
    }

    class ProtectedModeCodeBlockWrapper extends AbstractCodeBlockWrapper implements ProtectedModeCodeBlock
    {
	public ProtectedModeCodeBlockWrapper(ProtectedModeCodeBlock block)
	{
            super(block);
	}
    }
}
