/**
 * Copyright (c) 2005-2013 by Appcelerator, Inc. All Rights Reserved.
 * Licensed under the terms of the Eclipse Public License (EPL).
 * Please see the license.txt included with this distribution for details.
 * Any modifications to this file must keep this entire header intact.
 */
/*
 * Author: atotic
 * Created on Apr 28, 2004
 */
package org.python.pydev.debug.model;

import java.io.File;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.model.LineBreakpoint;
import org.eclipse.jface.text.IDocument;
import org.python.pydev.core.FileUtilsFileBuffer;
import org.python.pydev.core.ICodeCompletionASTManager;
import org.python.pydev.core.IModule;
import org.python.pydev.core.IPythonNature;
import org.python.pydev.core.log.Log;
import org.python.pydev.editor.codecompletion.revisited.modules.AbstractModule;
import org.python.pydev.editor.codecompletion.revisited.modules.SourceModule;
import org.python.pydev.parser.jython.SimpleNode;
import org.python.pydev.parser.visitors.NodeUtils;
import org.python.pydev.plugin.PydevPlugin;
import org.python.pydev.plugin.nature.PythonNature;
import org.python.pydev.shared_core.io.FileUtils;
import org.python.pydev.shared_core.structure.Tuple;

/**
 * Represents python breakpoint.
 *
 */
public class PyBreakpoint extends LineBreakpoint {
    /**
     * Marker attribute storing the path id of some external file
     */
    public static final String PY_BREAK_EXTERNAL_PATH_ID = "org.python.pydev.debug.PYDEV_EXTERNAL_PATH_ID";

    /**
     * Can be null/not set (which signals python-line) or django-line
     */
    public static final String PY_BREAK_TYPE = "org.python.pydev.debug.PY_BREAK_TYPE";
    public static final String PY_BREAK_TYPE_PYTHON = "python-line";
    public static final String PY_BREAK_TYPE_DJANGO = "django-line";

    static public final String PY_BREAK_MARKER = "org.python.pydev.debug.pyStopBreakpointMarker";

    static public final String DJANGO_BREAK_MARKER = "org.python.pydev.debug.djangoStopBreakpointMarker";

    static public final String PY_CONDITIONAL_BREAK_MARKER = "org.python.pydev.debug.pyConditionalStopBreakpointMarker";

    /**
     * Breakpoint attribute storing a breakpoint's conditional expression
     * (value <code>"org.eclipse.jdt.debug.core.condition"</code>). This attribute is stored as a
     * <code>String</code>.
     */
    protected static final String CONDITION = "org.python.pydev.debug.condition"; //$NON-NLS-1$
    /**
     * Breakpoint attribute storing a breakpoint's condition enablement
     * (value <code>"org.eclipse.jdt.debug.core.conditionEnabled"</code>). This attribute is stored as an
     * <code>boolean</code>.
     */
    protected static final String CONDITION_ENABLED = "org.python.pydev.debug.conditionEnabled";

    public static int nextId = 0;
    public static final Object lock = new Object();

    public final int breakpointId;

    public PyBreakpoint() {
        synchronized (lock) {
            this.breakpointId = nextId++;
        }
    }

    @Override
    public String getModelIdentifier() {
        return PyDebugModelPresentation.PY_DEBUG_MODEL_ID;
    }

    /**
     * @return the file to be debugged or null if it cannot be determined.
     */
    public String getFile() {
        IMarker marker = getMarker();
        IResource r = marker.getResource();
        if (r instanceof IFile) {
            IPath location = r.getLocation();
            if (location == null) {
                return null;
            }
            return location.toOSString();
        } else {
            //it's an external file...
            try {
                return (String) marker.getAttribute(PyBreakpoint.PY_BREAK_EXTERNAL_PATH_ID);
            } catch (CoreException e) {
                throw new RuntimeException(e);
            }
        }
    }

    private IDocument getDocument() {
        IMarker marker = getMarker();
        IResource r = marker.getResource();
        if (r instanceof IFile) {
            return FileUtilsFileBuffer.getDocFromResource(r);
        } else {
            //it's an external file...
            try {
                return FileUtilsFileBuffer
                        .getDocFromFile(new File((String) marker.getAttribute(PyBreakpoint.PY_BREAK_EXTERNAL_PATH_ID)));
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

    /**
     * @return The nature to be used for this breakpoint or null if it cannot be determined.
     */
    private IPythonNature getPythonNature() {
        IMarker marker = getMarker();
        IPythonNature nature = PythonNature.getPythonNature(marker.getResource());
        if (nature == null) {
            try {
                String externalPath = (String) marker.getAttribute(PyBreakpoint.PY_BREAK_EXTERNAL_PATH_ID);
                if (externalPath != null) {
                    Tuple<IPythonNature, String> infoForFile = PydevPlugin.getInfoForFile(new File(externalPath));
                    if (infoForFile != null) {
                        nature = infoForFile.o1;
                    }
                }
            } catch (CoreException e) {
                throw new RuntimeException(e);
            }
        }
        return nature;
    }

    public String getType() {
        IMarker marker = getMarker();
        Object attribute = null;
        if (marker != null) {
            try {
                attribute = marker.getAttribute(PyBreakpoint.PY_BREAK_TYPE);
            } catch (CoreException e) {
                Log.log(e);
            }
        }
        if (attribute != null
                && (attribute.equals(PyBreakpoint.PY_BREAK_TYPE_DJANGO) || attribute
                        .equals(PyBreakpoint.PY_BREAK_TYPE_PYTHON))) {
            return (String) attribute;
        }
        //default
        return PyBreakpoint.PY_BREAK_TYPE_PYTHON;
    }

    public Object getLine() {
        try {
            return getMarker().getAttribute(IMarker.LINE_NUMBER);
        } catch (CoreException e) {
            return "";
        }
    }

    public boolean supportsCondition() {
        return true;
    }

    public String getCondition() throws DebugException {
        return ensureMarker().getAttribute(CONDITION, null);
    }

    public boolean isConditionEnabled() throws DebugException {
        return ensureMarker().getAttribute(CONDITION_ENABLED, false);
    }

    public void setConditionEnabled(boolean conditionEnabled) throws CoreException {
        setAttributes(new String[] { CONDITION_ENABLED }, new Object[] { conditionEnabled });
    }

    public void setCondition(String condition) throws CoreException {
        if (condition != null && condition.trim().length() == 0) {
            condition = null;
        }
        setAttributes(new String[] { CONDITION }, new Object[] { condition });
    }

    /**
     * Returns the marker associated with this breakpoint.
     *
     * @return breakpoint marker
     * @exception DebugException if no marker is associated with
     *  this breakpoint or the associated marker does not exist
     */
    @Override
    protected IMarker ensureMarker() throws DebugException {
        IMarker m = getMarker();
        if (m == null || !m.exists()) {
            throw new DebugException(new Status(IStatus.ERROR, DebugPlugin.getUniqueIdentifier(),
                    DebugException.REQUEST_FAILED, "Breakpoint_no_associated_marker", null));
        }
        return m;
    }

    private String functionName;
    private long lastModifiedTimeCached;

    /**
     * @return the function name for this breakpoint.
     *
     * A return of "None" signals that we couldn't discover the function name (so, we should try to match things in the whole
     * file, and not only in the given context, as we don't know which context it is)
     */
    public String getFunctionName() {
        String fileStr = getFile();
        File file = fileStr != null ? new File(fileStr) : null;
        if (file == null || !file.exists()) {
            return "None";
        }

        if (FileUtils.lastModified(file) == lastModifiedTimeCached) {
            return functionName;
        }

        try {
            IPythonNature nature = getPythonNature();
            if (nature == null) {
                lastModifiedTimeCached = 0;
                return "None";
            }
            ICodeCompletionASTManager astManager = nature.getAstManager();
            if (astManager == null) {
                lastModifiedTimeCached = 0;
                return "None";
            }
            //Only mark it as found if we were able to get the python nature (otherwise, this could change later
            //if requesting during a setup)
            if (nature.startRequests()) { //start requests, as we'll ask for resolve and get module.
                SourceModule sourceModule = null;
                try {
                    String modName = nature.resolveModule(fileStr);
                    if (modName != null) {
                        //when all is set up, this is the most likely path we're going to use
                        //so, we shouldn't have delays when the module is changed, as it's already
                        //ok for use.
                        IModule module = astManager.getModule(modName, nature, true);
                        if (module instanceof SourceModule) {
                            sourceModule = (SourceModule) module;
                        }
                    }
                } finally {
                    nature.endRequests();
                }
                lastModifiedTimeCached = FileUtils.lastModified(file);

                if (sourceModule == null) {
                    //the text for the breakpoint requires the function name, and it may be requested before
                    //the ast manager is actually restored (so, modName is None, and we have little alternative
                    //but making a parse to get the function name)
                    IDocument doc = getDocument();
                    sourceModule = AbstractModule.createModuleFromDoc("", null, doc, nature, true);
                }

                int lineToUse = getLineNumber() - 1;

                if (sourceModule == null || sourceModule.getAst() == null || lineToUse < 0) {
                    functionName = "None";
                    return functionName;
                }

                SimpleNode ast = sourceModule.getAst();

                functionName = NodeUtils.getContextName(lineToUse, ast);
                if (functionName == null) {
                    functionName = ""; //global context
                }
                return functionName;

            }
            //If it was found, it would've already returned. So, match anything as we couldn't determine it.
            functionName = "None";

        } catch (Exception e) {
            //Some error happened determining it. Match anything.
            Log.log("Error determining breakpoint context. Breakpoint at: " + file + " will match any context.", e);
            functionName = "None";
        }
        return functionName;
    }

}
