/*
 * Created on 2004-08-17
 *
 * Part of the Symbian development plug-in for Eclipse
 * Copyright (C) Mikołaj Zalewski, 2004
 */
package pl.zalewski.mikolaj.symbdev;

import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.HashMap;
import java.util.Map;

import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.CommandLauncher;
import org.eclipse.cdt.core.model.ICModelMarker;
import org.eclipse.cdt.core.resources.ACBuilder;
import org.eclipse.cdt.core.resources.IConsole;
import org.eclipse.cdt.ui.CUIPlugin;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.ui.IPageLayout;
import org.eclipse.ui.IViewPart;
import org.eclipse.ui.IWorkbenchPage;

import pl.zalewski.mikolaj.symbdev.core.mmpmodel.BldInfModel;
import pl.zalewski.mikolaj.symbdev.errorparsers.CaseInsensitiveErrorParserManager;
import pl.zalewski.mikolaj.symbdev.properties.ProjectProperties;

/**
 * @author mikolaj
 *
 * This class runs the bldmake and abld commands to build the project
 */
public class ABLDBuilder extends ACBuilder {
    
    /**
     * Default constructor
     */
    public ABLDBuilder() {
        super();
    }

    /* (non-Javadoc)
     * @see org.eclipse.core.internal.events.InternalBuilder#build(int, java.util.Map, org.eclipse.core.runtime.IProgressMonitor)
     */
    protected IProject[] build(int kind, Map args, IProgressMonitor monitor)
            throws CoreException
    {
        OutputStream cos=null;
		if (monitor == null)
			monitor = new NullProgressMonitor();

		try
	    {
	        IProject project = getProject();
	        monitor.beginTask("Building "+project.getName(), 4);
	        String release=ProjectProperties.getABLDBuild(project);
		    Win32ToolEntry entry=ProjectProperties.getWin32Tool(project);
		    SymbianSDKEntry sdk=ProjectProperties.getSymbianSDK(project);
		    String workDir=SymbianCommandHelper.getWorkingDirectory(project, null, false);
		    BldInfModel.prepareForBuild(project);
		    
	        IConsole console = CCorePlugin.getDefault().getConsole();
	        console.start(project);
	        cos = console.getOutputStream();		    
		    
	        String precheckError=null;
		    if (workDir==null)
		    {
		        precheckError="Couldn't find build directory. Create the directory and" +
                	"the bld.inf file or change the setting in Project > " +
                	"Properties > Symbian OS development";
		    }
		    
		    char projectdrive=project.getFullPath().toOSString().charAt(0);
		    char sdkdrive=sdk.epocRoot.charAt(0);
		    
		    if (precheckError!=null &&
		        Character.isLetter(projectdrive) &&
		        Character.isLetter(sdkdrive) &&
		        projectdrive!=sdkdrive)
		    {
		        precheckError="SDK installed on a different drive than the project. " +
		        		"The Symbian build tools cannot work with a project on a " +
		        		"different drive. (You can create a new workspace on a " +
		        		"different drive with File > Switch Workspace)";
		    }
		    
		    if (precheckError!=null)
		    {
		        cos.write(("ERROR: "+precheckError+"\n").getBytes());
		        cos.flush();
		        addMarker(project, -1, precheckError, IMarker.SEVERITY_ERROR, null);
		        return null;
		    }

	        int work=2;
	        
	        // remove all markers for this project
	        removeAllMarkers(project);
	        
	        if (kind==FULL_BUILD)
	            work=1;
	        
	        if (kind==FULL_BUILD)
	        {
	            fAbldBatCache.remove(project);
		        if (!execute("Creating build structures...", monitor, cos, "bldmake", new String[]{"bldfiles"}))
		            return null;
		        monitor.worked(work);
		        if (!execute("Cleaning previous build...", monitor, cos, "abld", new String[]{"clean", entry.target, release}))
		            return null;
		        monitor.worked(work);
	        } else
	        {
		        ABLDBatCache cacheEntry=(ABLDBatCache)fAbldBatCache.get(project);
		        boolean abldCached=false;
		        if (cacheEntry!=null &&
		                cacheEntry.fSDK==sdk)
		        {
		            java.io.File f=new java.io.File(workDir, "abld.bat");
		            if (f.exists() && f.lastModified()==cacheEntry.fMTime)
		            {
		                abldCached=true;
		                cos.write("Build structures are up to date.\n".getBytes());
		            	cos.flush();
		            }
		        }
		        
		        if (!abldCached)
		        {
		            execute("Creating build structures...", monitor, cos, "bldmake", new String[]{"bldfiles"});
		            java.io.File f=new java.io.File(workDir, "abld.bat");
		            if (f.exists())
		            {
		                cacheEntry=new ABLDBatCache();
		                cacheEntry.fSDK=sdk;
			            cacheEntry.fMTime=f.lastModified();
			            fAbldBatCache.put(project, cacheEntry);
		            } else System.out.println("ABLD.BAT not found!");
		        }
		        monitor.worked(work);	            
	        }

	        boolean stopOnError=ProjectProperties.getStopOnError(project);
	        String[] buildCommand=new String[stopOnError?4:5];
	        int arg=0;
	        buildCommand[arg++]="-v";
	        if (!stopOnError)
	            buildCommand[arg++]="-k";
	        buildCommand[arg++]="build";
	        buildCommand[arg++]=entry.target;
	        buildCommand[arg++]=release;
	        execute("ABLD BUILD...", monitor, cos, "abld", buildCommand);
	        monitor.worked(work);

	        OutputStreamWriter writer=new OutputStreamWriter(cos);
	        writer.write("\nBUILD FINISHED. Check Problems page for eventual errors/warnings.\n");
	        writer.flush();
	        
	        CUIPlugin.getStandardDisplay().asyncExec(new Runnable()
	                {
	            		public void run()
	            		{
	            	        IWorkbenchPage page=CUIPlugin.getActivePage();
	            	        if (page==null)
	            	            return;
	            	        IViewPart problems=page.findView(IPageLayout.ID_PROBLEM_VIEW);
	            	        if (problems==null)
	            	            return;
	            	        page.bringToTop(problems);	            		    
	            		}
	                });
	    }
		catch (IOException e) {
		    logIOException(e);
		}
	    finally
	    {
	        monitor.done();
	        if (cos!=null)
	            try {
	                cos.close();
	            }
	        	catch (IOException e) {
	        	    logIOException(e);
	        	}
	    }
        return null;
    }

	protected void clean(IProgressMonitor monitor) throws CoreException
	{
	    OutputStream cos=null;
		if (monitor == null)
			monitor = new NullProgressMonitor();

		try {
	        IProject project = getProject();
	        monitor.beginTask("Cleaning "+project.getName(), 100); //$NON-NLS-1$

	        IConsole console = CCorePlugin.getDefault().getConsole();
	        console.start(project);
	        cos = console.getOutputStream();
	        
	        // remove all markers for this project
	        removeAllMarkers(project);

	        if (!execute("Creating build structures...", monitor, cos, "bldmake", new String[]{"bldfiles"}))
	            return;
	        monitor.worked(33);

	        if (!execute("Removing build files...", monitor, cos, "abld", new String[]{"clean"}))
	            return;
	        monitor.worked(33);

	        if (!execute("Removing build structures...", monitor, cos, "bldmake", new String[]{"clean"}))
	            return;
	        monitor.worked(33);	        
		}
		finally {
	        monitor.done();
	        if (cos!=null)
	            try {
	                cos.close();
	            }
	        	catch (IOException e) {
	        	    logIOException(e);
	        	}
	    }
	}
	
	protected boolean execute(String desc, IProgressMonitor monitor, OutputStream cos, String cmd, String[] args)
		throws CoreException
	{
	    boolean cancelled=false;
	    try
	    {
	        cos.write((desc+"\n").getBytes());
	        cos.flush();
	    }
	    catch (IOException e)
	    {
	        e.printStackTrace();
	    }
	
	    IProject project=getProject();
	    String errMsg=null;

		CaseInsensitiveErrorParserManager epm = new CaseInsensitiveErrorParserManager(project, SymbianCommandHelper.getWorkingDirectoryPath(project), this, getParserIds());
		epm.setOutputStream(cos);		
	    
		SymbianCommandLauncher launcher = new SymbianCommandLauncher();
		launcher.showCommand(true);
		
		Process p = launcher.execute(project, cmd, args, ProjectProperties.getUseInternalCmd(project));
		if (p != null) {
			try {
				// Close the input of the Process explicitely.
				// We will never write to it.
				p.getOutputStream().close();
			} catch (IOException e) {
			}
			// Before launching give visual cues via the monitor
			monitor.subTask("Running "+launcher.getCommandLine());
			int status=launcher.waitAndRead(epm.getOutputStream(), epm.getOutputStream(),
			        new SubProgressMonitor(monitor, 0));
			if (status!=CommandLauncher.OK)
			    if (status==CommandLauncher.COMMAND_CANCELED) {
			        addMarker(project, -1, "Compilation cancelled", IMarker.SEVERITY_ERROR, null);
			        cancelled=true;
			    } else errMsg=launcher.getErrorMessage();
				
			
//			if (p.exitValue()!=0)
//			    epm.generateMarker(null, -1, "Command "+launcher.getCommandLine()+" returned "+p.exitValue(), SEVERITY_ERROR_BUILD, null);
		} else {
			errMsg=launcher.getErrorMessage();
		}
		
		try {
            epm.close();
            epm.close();
        } catch (IOException e1) {
            logIOException(e1);
        }
		epm.reportProblems();		
		
		if (errMsg!=null)
		{
			StringBuffer buf = new StringBuffer(cmd + " "); //$NON-NLS-1$
			for (int i = 0; i < args.length; i++) {
				buf.append(args[i]);
				buf.append(' ');
			}

			buf = new StringBuffer("Error while executing '"+buf+"': ");
			buf.append(System.getProperty("line.separator", "\n")); //$NON-NLS-1$ //$NON-NLS-2$
			buf.append(errMsg);
			throw new CoreException(new Status(IStatus.ERROR, SymbdevPlugin.getUniqueIdentifier(),
			        SymbdevPlugin.ERROR_BUILD_EXEC, buf.toString(), null));
		}
		return !cancelled;
	}

	/**
     * @return The table of error parsers that should be used
     * to parse the output
     */
    private String[] getParserIds() {
        String[] tab=new String[3];
        tab[0]="org.eclipse.cdt.core.MakeErrorParser";
        tab[1]="pl.zalewski.mikolaj.symbdev.NMakeErrorParser";
        tab[2]="pl.zalewski.mikolaj.symbdev.AdaptiveErrorParser";
        return tab;
    }	
	
	private void removeAllMarkers(IProject currProject) throws CoreException {
		IWorkspace workspace = currProject.getWorkspace();

		// remove all markers
		IMarker[] markers = currProject.findMarkers(ICModelMarker.C_MODEL_PROBLEM_MARKER, true, IResource.DEPTH_INFINITE);
		if (markers != null) {
			workspace.deleteMarkers(markers);
		}
	}
	
	private void logIOException(IOException e) {
        SymbdevPlugin.getDefault().getLog().log(
                SymbdevPlugin.createErrorStatus(SymbdevPlugin.ERROR_IO_ACCESS,
                        "I/O exception", e));	    
	}
	
	static class ABLDBatCache
	{
	    SymbianSDKEntry fSDK;
	    long fMTime;
	};
	
	/**
	 * Maps projects to ABLDBatCahce used to create the ABLD.BAT during this
	 * session. If the SDK and mTime is the same, creation of ABLD.BAT is
	 * skipped
	 */
	HashMap fAbldBatCache=new HashMap();
}
