/*
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed under the Eclipse Public License, Version 1.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.eclipse.org/org/documents/epl-v10.php
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.ide.eclipse.adt.internal.editors.layout;

import com.android.ide.eclipse.adt.AndroidConstants;
import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFolder;
import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFolderType;
import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
import com.android.ide.eclipse.adt.internal.resources.manager.ResourceMonitor;
import com.android.ide.eclipse.adt.internal.resources.manager.ResourceMonitor.IFileListener;
import com.android.ide.eclipse.adt.internal.resources.manager.ResourceMonitor.IResourceEventListener;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarkerDelta;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResourceDelta;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 * Monitor for file changes triggering a layout redraw.
 */
public final class LayoutReloadMonitor implements IFileListener, IResourceEventListener {
    
    // singleton, enforced by private constructor.
    private final static LayoutReloadMonitor sThis = new LayoutReloadMonitor();
    
    /**
     * Map of listeners by IProject.
     */
    private final Map<IProject, List<ILayoutReloadListener>> mListenerMap =
        new HashMap<IProject, List<ILayoutReloadListener>>();
    
    private final static int CHANGE_CODE = 0;
    private final static int CHANGE_RESOURCES = 1;
    private final static int CHANGE_R = 2;
    private final static int CHANGE_COUNT = 3;
    /**
     * List of projects having received a file change. the boolean[] contains 3 values:
     * <ul><li>CHANGE_CODE: code change flag.</li>
     * <li>CHANGE_RESOURCES: resource change flag.</li>
     * <li>CHANGE_R: R clas change flag</li></ul>
     */
    private final Map<IProject, boolean[]> mChangedProjects = new HashMap<IProject, boolean[]>();
    
    /**
     * Classes which implement this interface provide a method to respond to resource changes
     * triggering a layout redraw
     */
    public interface ILayoutReloadListener {
        /**
         * Sent when the layout needs to be redrawn
         * @param codeChange The trigger happened due to a code change.
         * @param rChange The trigger happened due to a change in the R class.
         * @param resChange The trigger happened due to a resource change.
         */
        void reloadLayout(boolean codeChange, boolean rChange, boolean resChange); 
    }
    
    /**
     * Returns the single instance of {@link LayoutReloadMonitor}.
     */
    public static LayoutReloadMonitor getMonitor() {
        return sThis;
    }
    
    private LayoutReloadMonitor() {
        ResourceMonitor monitor = ResourceMonitor.getMonitor();
        monitor.addFileListener(this, IResourceDelta.ADDED | IResourceDelta.CHANGED);
        monitor.addResourceEventListener(this);
    }
    
    /**
     * Adds a listener for a given {@link IProject}.
     * @param project
     * @param listener
     */
    public void addListener(IProject project, ILayoutReloadListener listener) {
        synchronized (mListenerMap) {
            List<ILayoutReloadListener> list = mListenerMap.get(project);
            if (list == null) {
                list = new ArrayList<ILayoutReloadListener>();
                mListenerMap.put(project, list);
            }
            
            list.add(listener);
        }
    }
    
    /**
     * Removes a listener for a given {@link IProject}.
     * @param project
     * @param listener
     */
    public void removeListener(IProject project, ILayoutReloadListener listener) {
        synchronized (mListenerMap) {
            List<ILayoutReloadListener> list = mListenerMap.get(project);
            if (list != null) {
                list.remove(listener);
            }
        }
    }

    /*
     * (non-Javadoc)
     * @see com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IFileListener#fileChanged(org.eclipse.core.resources.IFile, org.eclipse.core.resources.IMarkerDelta[], int)
     * 
     * Callback for ResourceMonitor.IFileListener. Called when a file changed.
     * This records the changes for each project, but does not notify listeners.
     * @see #resourceChangeEventEnd
     */
    public void fileChanged(IFile file, IMarkerDelta[] markerDeltas, int kind) {
        // get the file project
        IProject project = file.getProject();

        // if this project has already been marked as modified, we do nothing.
        boolean[] changeFlags = mChangedProjects.get(project);
        if (changeFlags != null && changeFlags[CHANGE_CODE] && changeFlags[CHANGE_RESOURCES] &&
                changeFlags[CHANGE_R]) {
            return;
        }
        
        // now check that the file is *NOT* a layout file (those automatically trigger a layout
        // reload and we don't want to do it twice.
        ResourceFolder resFolder = ResourceManager.getInstance().getResourceFolder(file);
        if (resFolder != null) {
            if (resFolder.getType() != ResourceFolderType.LAYOUT) {
                // this is a resource change!
                if (changeFlags == null) {
                    changeFlags = new boolean[CHANGE_COUNT];
                    mChangedProjects.put(project, changeFlags);
                }
    
                changeFlags[CHANGE_RESOURCES] = true;
            }
        } else if (AndroidConstants.EXT_CLASS.equals(file.getFileExtension())) {
            if (file.getName().matches("R[\\$\\.](.*)")) {
                // this is a R change!
                if (changeFlags == null) {
                    changeFlags = new boolean[CHANGE_COUNT];
                    mChangedProjects.put(project, changeFlags);
                }

                changeFlags[CHANGE_R] = true;
            } else {
                // this is a code change!
                if (changeFlags == null) {
                    changeFlags = new boolean[CHANGE_COUNT];
                    mChangedProjects.put(project, changeFlags);
                }

                changeFlags[CHANGE_CODE] = true;
            }
        }
    }
    
    /*
     * (non-Javadoc)
     * @see com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IResourceEventListener#resourceChangeEventStart()
     * 
     * Callback for ResourceMonitor.IResourceEventListener. Called at the beginning of a resource
     * change event. This is called once, while fileChanged can be called several times.
     * 
     */
    public void resourceChangeEventStart() {
        // nothing to be done here, it all happens in the resourceChangeEventEnd
    }

    /*
     * (non-Javadoc)
     * @see com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IResourceEventListener#resourceChangeEventEnd()
     * 
     * Callback for ResourceMonitor.IResourceEventListener. Called at the end of a resource
     * change event. This is where we notify the listeners.
     */
    public void resourceChangeEventEnd() {
        // for each IProject that was changed, we notify all the listeners.
        synchronized (mListenerMap) {
            for (Entry<IProject, boolean[]> project : mChangedProjects.entrySet()) {
                List<ILayoutReloadListener> listeners = mListenerMap.get(project.getKey());
                
                boolean[] flags = project.getValue();
                
                if (listeners != null) {
                    for (ILayoutReloadListener listener : listeners) {
                        listener.reloadLayout(flags[CHANGE_CODE], flags[CHANGE_R],
                                flags[CHANGE_RESOURCES]);
                    }
                }
            }
        }
        
        // empty the list.
        mChangedProjects.clear();
    }
}
