/*
    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;

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

public class MemoryManager implements HardwareComponent
{
    private PhysicalAddressSpace physicalAddr;
    private LinearAddressSpace linearAddr;
    private Processor cpu;
    private boolean factoriesValid;

    private CodeBlockFactory realModeChain, protectedModeChain;
    private CodeBlockFactory optimisedRealModeChain, optimisedProtectedModeChain;
    private CodeBlockFactory compiledChain;
    private CodeBlockCombiner combiner;
    private ByteSourceWrappedMemory byteSource;

    private static MemoryManager singletonInstance;

    private RealModeCodeBlock spanningRealMode;
    private ProtectedModeCodeBlock spanningProtectedMode;

    static 
    {
	singletonInstance = new MemoryManager();
    }

    public static MemoryManager getInstance()
    {
	return singletonInstance;
    }

    private MemoryManager()
    {
        buildFactories();
    }

    private synchronized void buildFactories()
    {
        byteSource = new ByteSourceWrappedMemory();

        realModeChain = new DefaultCodeBlockFactory(new RealModeDecoder(), new BasicCompiler());
	optimisedRealModeChain = new DefaultCodeBlockFactory(new RealModeUDecoder(), new OptimisedCompiler());

        //cachedRealModeChain = new DefaultCodeBlockFactory(new RealModeUDecoder(), new CachedInstructionCompiler(new OptimisedCompiler()));
	//compiledChain = new CachedByteCodeBlockFactory(new RealModeUDecoder(), new OptimisedCompiler());
	
        //compiledChain = new CachedByteCodeBlockFactory(new RealModeUDecoder(), new org.jpc.sourcecompiler.JavaSourceCompiler());
	//compiledChain = new CachedByteCodeBlockFactory(new RealModeUDecoder(), new QandDCompiler());
	//compiledChain = new CachedByteCodeBlockFactory(new RealModeUDecoder(), new ByteCodeCompiler());

        //BackgroundCompiler bgc = new BackgroundCompiler(new OptimisedCompiler(), new org.jpc.sourcecompiler.JavaSourceCompiler());
        //BackgroundCompiler bgc = new BackgroundCompiler(new OptimisedCompiler(), new QandDCompiler());
        BackgroundCompiler bgc = new BackgroundCompiler(new OptimisedCompiler(), new ByteCodeCompiler());
        compiledChain = new DefaultCodeBlockFactory(new RealModeUDecoder(), bgc);
        //compiledChain = new CachedByteCodeBlockFactory(new RealModeUDecoder(), bgc);
        combiner = new CodeBlockCombiner(new CompositeFactory());
        //combiner = new CodeBlockCombiner(new DefaultCodeBlockFactory(new RealModeUDecoder(), bgc));
        //combiner = new CodeBlockCombiner(realModeChain);
        //combiner = new CodeBlockCombiner(compiledChain);

	protectedModeChain = new DefaultCodeBlockFactory(new ProtectedModeDecoder(), new BasicCompiler());
 	//cachedProtectedModeChain = new DefaultCodeBlockFactory(new ProtectedModeUDecoder(), new CachedInstructionCompiler(new OptimisedCompiler()));
	//cachedProtectedModeChain = new CachedByteCodeBlockFactory(new ProtectedModeUDecoder(), new OptimisedCompiler());
	optimisedProtectedModeChain = new DefaultCodeBlockFactory(new ProtectedModeUDecoder(), new OptimisedCompiler());

	spanningRealMode = new SpanningRealModeCodeBlock(new CodeBlockFactory[]{optimisedRealModeChain, realModeChain});
	spanningProtectedMode = new SpanningProtectedModeCodeBlock(new CodeBlockFactory[]{optimisedProtectedModeChain, protectedModeChain});
	factoriesValid = true;
        //debugFactory = new org.jpc.sourcecompiler.DebugCompilerFactory(protectedModeChain);
    }

    class CompositeFactory implements CodeBlockFactory
    {
        private RealModeCodeBlock tryFactory(CodeBlockFactory ff, ByteSource source, RealModeCodeBlock spanningBlock)
        {
            try 
            {
                return ff.getRealModeCodeBlock(source);
            } 
            catch(ArrayIndexOutOfBoundsException e) 
            {
                return spanningBlock;
            } 
            catch (Exception e)
            {
                return null;
            }
        }

        public RealModeCodeBlock getRealModeCodeBlock(ByteSource source)
        {
            RealModeCodeBlock block = tryFactory(compiledChain, source, spanningRealMode);
            if (block != null)
                return block;

            source.reset();
            block = tryFactory(optimisedRealModeChain, source, spanningRealMode);
            if (block != null)
                return block;

            source.reset();
            return tryFactory(realModeChain, source, spanningRealMode);
            
        }
        
        public ProtectedModeCodeBlock getProtectedModeCodeBlock(ByteSource source, boolean operandSize)
        {
            return null;
        }
    }

    public ObjectTreeCache getObjectTreeCache()
    {
	return null;
    }

    private RealModeCodeBlock tryFactory(CodeBlockFactory ff, Memory memory, int offset, RealModeCodeBlock spanningBlock)
    {
        try 
        {
            byteSource.set(memory, offset & AddressSpace.BLOCK_MASK);
            return ff.getRealModeCodeBlock(byteSource);
        } 
        catch(ArrayIndexOutOfBoundsException e) 
        {
	    return spanningBlock;
        } 
        catch (Exception e)
        {
            return null;
        }
    }

    public RealModeCodeBlock getRealModeCodeBlockAt(Memory memory, int offset)
    {
        RealModeCodeBlock block = null;
        block = combiner.getRealModeCodeBlockAt(memory, offset);
        if (block != null)
        {
            ((CodeBlockMemory)memory).setCodeBlockAt(offset & AddressSpace.BLOCK_MASK, block);
            return block;
        }
        
	byteSource.set(memory, offset & AddressSpace.BLOCK_MASK);
	block = tryFactory(compiledChain, memory, offset, spanningRealMode);
        if (block == null)
            block = tryFactory(optimisedRealModeChain, memory, offset, spanningRealMode);
        if (block == null)
            block = tryFactory(realModeChain, memory, offset, spanningRealMode);

	((CodeBlockMemory)memory).setCodeBlockAt(offset & AddressSpace.BLOCK_MASK, block);
	return block;
    }

    public ProtectedModeCodeBlock getProtectedModeCodeBlockAt(Memory memory, int offset)
    {
	byteSource.set(memory, offset & AddressSpace.BLOCK_MASK);
	ProtectedModeCodeBlock block = null;
	try 
        {
            block = optimisedProtectedModeChain.getProtectedModeCodeBlock(byteSource, cpu.cs.getDefaultSizeFlag());
            //block = cachedProtectedModeChain.getProtectedModeCodeBlock(byteSource, cpu.cs.getDefaultSizeFlag());
        } 
        catch(ArrayIndexOutOfBoundsException e) 
        {
            block = spanningProtectedMode;
        } 
        catch (Exception e) 
        {
            try 
            {
                byteSource.set(memory, offset & AddressSpace.BLOCK_MASK);
                block = protectedModeChain.getProtectedModeCodeBlock(byteSource, cpu.cs.getDefaultSizeFlag());
            } 
            catch (ArrayIndexOutOfBoundsException f) 
            {
                block = spanningProtectedMode;
            }
        }

	((CodeBlockMemory)memory).setCodeBlockAt(offset & AddressSpace.BLOCK_MASK, block);
	return block;
    }

    public Memory convertMemory(Memory memory)
    {
	if (!(memory instanceof LazyCodeBlockMemory)) 
        {
	    LazyCodeBlockMemory newMemory = new LazyCodeBlockMemory(memory);
	    physicalAddr.replaceBlocks(memory, newMemory);
	    linearAddr.replaceBlocks(memory, newMemory);
	    return newMemory;
	}
	return memory;
    }

    public CodeBlock handleCodeBlockReplacement(int address, CodeBlockReplacementException ex)
    {
        CodeBlock replacement = ex.getReplacement();
	Memory memory = physicalAddr.getReadMemoryBlockAt(address);
        int offset = address & AddressSpace.BLOCK_MASK;

        ((CodeBlockMemory)memory).setCodeBlockAt(offset, replacement);
        return replacement;
    }

    public CodeBlock[] getCodeBlockCacheArray(CodeBlockMemory mem)
    {
	return new CodeBlock[(int)(mem.getSize())];
    }

    public void reset()
    {
	physicalAddr = null;
	linearAddr = null;
	cpu = null;
    }

    public synchronized void flushReferences()
    {
        physicalAddr = null;
        linearAddr = null;
        cpu = null;
        
        realModeChain = protectedModeChain = optimisedRealModeChain = optimisedProtectedModeChain = compiledChain = null;
        spanningRealMode = null;
        spanningProtectedMode = null;

        byteSource = null;
        factoriesValid = false;
    }

    public boolean initialised()
    {
	return (factoriesValid) && (physicalAddr != null) && (linearAddr != null) && (cpu != null);
    }

    public void acceptComponent(HardwareComponent component)
    {
        synchronized (this)
        {
            if (!factoriesValid)
                buildFactories();
        }

	if (component instanceof PhysicalAddressSpace)
	    physicalAddr = (PhysicalAddressSpace)component;
	if (component instanceof LinearAddressSpace)
	    linearAddr = (LinearAddressSpace)component;
	if (component instanceof Processor)
	    cpu = (Processor)component;
    }
}
