/*******************************************************************************
 * Copyright (c) 2004, 2008 QNX Software Systems and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 * QNX Software Systems - Initial API and implementation
 * Mike Caraman (Frescale) - https://bugs.eclipse.org/bugs/show_bug.cgi?id=210863
 *******************************************************************************/
package org.eclipse.cdt.debug.internal.core.model;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.LineNumberReader;

import org.eclipse.cdt.core.IAddress;
import org.eclipse.cdt.core.IAddressFactory;
import org.eclipse.cdt.core.model.ITranslationUnit;
import org.eclipse.cdt.debug.core.cdi.model.ICDIInstruction;
import org.eclipse.cdt.debug.core.cdi.model.ICDIMixedInstruction;
import org.eclipse.cdt.debug.core.model.IAsmInstruction;
import org.eclipse.cdt.debug.core.model.IAsmSourceLine;
import org.eclipse.cdt.debug.core.model.ICStackFrame;
import org.eclipse.cdt.debug.core.model.IDisassembly;
import org.eclipse.cdt.debug.core.model.IDisassemblyBlock;
import org.eclipse.cdt.debug.core.model.IExecFileInfo;
import org.eclipse.cdt.debug.core.sourcelookup.ICSourceLocator;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IStorage;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.debug.core.model.ISourceLocator;
import org.eclipse.debug.core.sourcelookup.ISourceLookupDirector;

/**
 * CDI-based implementation of <code>IDisassemblyBlock</code>.
 */
public class DisassemblyBlock implements IDisassemblyBlock, IAdaptable {

	private IDisassembly fDisassembly;

	private Object fSourceElement;

	private IAsmSourceLine[] fSourceLines;

	private IAddress fStartAddress = null;

	private IAddress fEndAddress = null;
	
	private boolean fMixedMode = false;

	/**
	 * Constructor for DisassemblyBlock.
	 */
	private DisassemblyBlock( IDisassembly disassembly ) {
		fDisassembly = disassembly;
	}

	public static DisassemblyBlock create( IDisassembly disassembly, ICDIMixedInstruction[] instructions ) {
		DisassemblyBlock block = new DisassemblyBlock( disassembly );
		ISourceLocator locator = disassembly.getDebugTarget().getLaunch().getSourceLocator();
		IAddressFactory factory = ((CDebugTarget)disassembly.getDebugTarget()).getAddressFactory();
		block.initialize( factory, locator, instructions );
		return block;
	}

	public static DisassemblyBlock create( IDisassembly disassembly, ICDIInstruction[] instructions ) {
		DisassemblyBlock block = new DisassemblyBlock( disassembly );
		IAddressFactory factory = ((CDebugTarget)disassembly.getDebugTarget()).getAddressFactory();
		block.initialize( factory, instructions );
		return block;
	}

	private void initialize( IAddressFactory factory, ICDIInstruction[] instructions ) {
		setMixedMode( false );
		createSourceLines( factory, instructions );
		initializeAddresses();
	}

	private void initialize( IAddressFactory factory, ISourceLocator locator, ICDIMixedInstruction[] mi ) {
		setMixedMode( true );
		createSourceLines( factory, locator, mi );
		initializeAddresses();
	}

	/* (non-Javadoc)
	 * @see org.eclipse.cdt.debug.core.model.IDisassemblyBlock#getDisassembly()
	 */
	@Override
	public IDisassembly getDisassembly() {
		return fDisassembly;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.cdt.debug.core.model.IDisassemblyBlock#getModuleFile()
	 */
	@Override
	public String getModuleFile() {
		IDisassembly d = getDisassembly();
		if ( d != null ) {
			IExecFileInfo info = (IExecFileInfo)d.getAdapter( IExecFileInfo.class );
			if ( info != null && info.getExecFile() != null ) {
				return info.getExecFile().getPath().toOSString();
			}
		}
		return ""; //$NON-NLS-1$
	}

	/* (non-Javadoc)
	 * @see org.eclipse.cdt.debug.core.model.IDisassemblyBlock#getSourceElement()
	 */
	@Override
	public Object getSourceElement() {
		return fSourceElement;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.cdt.debug.core.model.IDisassemblyBlock#contains(org.eclipse.cdt.debug.core.model.ICStackFrame)
	 */
	@Override
	public boolean contains( ICStackFrame frame ) {
		if ( !getDisassembly().getDebugTarget().equals( frame.getDebugTarget() ) )
			return false;
		if ( fStartAddress == null || fEndAddress == null )
			return false;
		IAddress address = frame.getAddress();	// will return null if frame was disposed
		return (address != null && address.compareTo( fStartAddress ) >= 0 && address.compareTo( fEndAddress ) <= 0);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.cdt.debug.core.model.IDisassemblyBlock#getSourceLines()
	 */
	@Override
	public IAsmSourceLine[] getSourceLines() {
		return fSourceLines;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class)
	 */
	@Override
	public Object getAdapter( Class adapter ) {
		return null;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.cdt.debug.core.model.IDisassemblyBlock#isMixedMode()
	 */
	@Override
	public boolean isMixedMode() {
		return fMixedMode;
	}

	public void dispose() {
	}

	private void createSourceLines( IAddressFactory factory, ISourceLocator locator, ICDIMixedInstruction[] mi ) {
		IAsmSourceLine[] result = new IAsmSourceLine[mi.length];
		LineNumberReader reader = null;
		if ( result.length > 0 && locator != null ) {
			String fileName = mi[0].getFileName();
			Object element = null;
			if ( locator instanceof ISourceLookupDirector ) {
				element = ((ISourceLookupDirector)locator).getSourceElement( fileName );
			}
			if ( locator instanceof ICSourceLocator ) {
				element = ((ICSourceLocator)locator).findSourceElement( fileName );
			}
			fSourceElement = element;
			File file = null;
			if ( element instanceof IFile ) {
				file = ((IFile)element).getLocation().toFile();
			}
			else if ( element instanceof ITranslationUnit ) {
                file = ((ITranslationUnit)element).getLocation().toFile();
            }
			else if ( element instanceof IStorage ) {
				file = ((IStorage)element).getFullPath().toFile();
			}
			if ( file != null ) {
				try {
					reader = new LineNumberReader( new FileReader( file ) );
				}
				catch( FileNotFoundException e ) {
				}				
			}
		}
		for ( int i = 0; i < result.length; ++i ) {
			String text = null;
			boolean failed = false;
			int lineNumber = mi[i].getLineNumber();
			if ( reader != null ) {
				while( reader.getLineNumber() + 1 < lineNumber ) {
					try {
						if ( reader.readLine() == null ) {
							// break if the end of file is reached (see bug #123745)
							failed = true;
							break;
						}
					}
					catch( IOException e ) {
					}
				}
				if ( !failed && reader.getLineNumber() + 1 == lineNumber ) {
					try {
						text = reader.readLine() + '\n';
					}
					catch( IOException e ) {
					}
				}
			}
			result[i] = new AsmSourceLine( factory, text, lineNumber, mi[i].getInstructions() );
		}
		fSourceLines = result;
	}

	private void createSourceLines( IAddressFactory factory, ICDIInstruction[] instructions ) {
		fSourceLines = new IAsmSourceLine[] { new AsmSourceLine( factory, "", instructions ) }; //$NON-NLS-1$
	}

	private void initializeAddresses() {
		for ( int i = 0; i < fSourceLines.length; ++i ) {
			IAsmInstruction[] instr = fSourceLines[i].getInstructions();
			if ( instr.length > 0 ) {
				if ( fStartAddress == null )
					fStartAddress = instr[0].getAdress();
				fEndAddress = instr[instr.length - 1].getAdress();
			}
		}
	}

	private void setMixedMode( boolean mixedMode ) {
		this.fMixedMode = mixedMode;
	}

	protected boolean contains( IAddress address ) {
		return ( address.compareTo( fStartAddress ) >= 0 && address.compareTo( fEndAddress ) <= 0 );
	}
}
