/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
 *
 * 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 org.netbeans.modules.web.core.syntax.completion;

import org.netbeans.modules.web.core.syntax.completion.api.JspCompletionItem;
import java.util.*;
import java.beans.*;
import java.io.Closeable;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.text.JTextComponent;
import javax.swing.ImageIcon;
import org.netbeans.modules.editor.NbEditorUtilities;
import org.netbeans.modules.web.jsps.parserapi.PageInfo;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileStateInvalidException;
import org.openide.filesystems.FileSystem;
import org.netbeans.modules.web.core.syntax.*;
import org.netbeans.modules.web.core.syntax.completion.JavaJspCompletionProvider.CompletionQueryDelegatedToJava;
import org.netbeans.spi.editor.completion.CompletionItem;
import org.netbeans.spi.editor.completion.CompletionProvider;
import org.openide.util.ImageUtilities;


/** Support for code completion of default JSP tags.
 *
 * @author  pjiricka
 * @author  Marek Fukala
 */
public class AttrSupports {
    
    private static final Logger logger = Logger.getLogger(AttrSupports.class.getName());
    
    public static class ScopeSupport extends AttributeValueSupport.Default {
        
        public ScopeSupport(boolean tag, String longName, String attrName) {
            super(tag, longName, attrName);
        }
        
        @Override
        protected List<String> possibleValues(JspSyntaxSupport sup, SyntaxElement.TagDirective item) {
            ArrayList<String> list = new ArrayList<String>();
            list.add("application");    // NOI18N
            list.add("page");           // NOI18N
            list.add("request");        // NOI18N
            list.add("session");        // NOI18N
            return list;
        }
        
    }

    public static class BodyContentSupport extends AttributeValueSupport.Default {

        public BodyContentSupport(boolean tag, String longName, String attrName) {
            super(tag, longName, attrName);
        }

        @Override
        protected List<String> possibleValues(JspSyntaxSupport sup, SyntaxElement.TagDirective item) {
            ArrayList<String> list = new ArrayList<String>();
            list.add("scriptless");    // NOI18N
            list.add("tagdependent");           // NOI18N
            list.add("empty");        // NOI18N
            return list;
        }

    }
    
    public static class RootVersionSupport extends AttributeValueSupport.Default {
        
        public RootVersionSupport(boolean tag, String longName, String attrName) {
            super(tag, longName, attrName);
        }
        
        @Override
        protected List<String> possibleValues(JspSyntaxSupport sup, SyntaxElement.TagDirective item) {
            ArrayList<String> list = new ArrayList<String>();
            list.add("1.2");    // NOI18N
            list.add("2.0");           // NOI18N
            return list;
        }
        
    }
    
    public static class PluginTypeSupport extends AttributeValueSupport.Default {
        
        public PluginTypeSupport(boolean tag, String longName, String attrName) {
            super(tag, longName, attrName);
        }
        
        @Override
        protected List<String> possibleValues(JspSyntaxSupport sup, SyntaxElement.TagDirective item) {
            ArrayList<String> list = new ArrayList<String>();
            list.add("bean");    // NOI18N
            list.add("applet");           // NOI18N
            return list;
        }
        
    }
    
    public static class VariableScopeSupport extends AttributeValueSupport.Default {
        
        public VariableScopeSupport(boolean tag, String longName, String attrName) {
            super(tag, longName, attrName);
        }
        
        @Override
        protected List<String> possibleValues(JspSyntaxSupport sup, SyntaxElement.TagDirective item) {
            ArrayList<String> list = new ArrayList<String>();
            list.add("AT_BEGIN");    // NOI18N
            list.add("AT_END");           // NOI18N
            list.add("NESTED");        // NOI18N
            return list;
        }
        
    }
    
    public static class YesNoTrueFalseSupport extends AttributeValueSupport.Default {
        
        public YesNoTrueFalseSupport(boolean tag, String longName, String attrName) {
            super(tag, longName, attrName);
        }
        
        @Override
        protected List<String> possibleValues(JspSyntaxSupport sup, SyntaxElement.TagDirective item) {
            ArrayList<String> list = new ArrayList<String>();
            list.add("false");    // NOI18N
            list.add("no");           // NOI18N
            list.add("true");        // NOI18N
            list.add("yes");        // NOI18N
            return list;
        }
        
    }
    
    /**
     * Provides code completion for a class name context
     */
    @Deprecated
    public static class ClassNameSupport extends AttributeValueSupport.Default {
        
        public ClassNameSupport(boolean tag, String longName, String attrName) {
            super(tag, longName, attrName);
        }
        
        @Override
        protected List possibleValues(JspSyntaxSupport sup, SyntaxElement.TagDirective item) {
            return new ArrayList();
        }
        
        protected String getFakedClassBody(String prefix){
            return "class Foo extends " + prefix; //NOI18N
        }
        
        /** Returns the complete result that contains elements from getCompletionItems.   */
        @Override
        public void result(JspCompletionQuery.CompletionResultSet result, JTextComponent component, int offset,
                JspSyntaxSupport sup, SyntaxElement.TagDirective item, String valuePart) {
            
            String fakedClassBody = getFakedClassBody(valuePart);
            int shiftedOffset = fakedClassBody.length();
            
            logger.fine("JSP CC: delegating CC query to java file:\n" //NOI18N
                            + fakedClassBody.substring(0, shiftedOffset)
                            + "|" + fakedClassBody.substring(shiftedOffset) + "\n"); //NOI18N
            
            CompletionQueryDelegatedToJava delegate = new CompletionQueryDelegatedToJava(
                    offset, shiftedOffset, CompletionProvider.COMPLETION_QUERY_TYPE);
            
            delegate.create(component.getDocument(), fakedClassBody);
            List<? extends CompletionItem> items =  delegate.getCompletionItems();
            result.addAllItems(items);
            result.setAnchorOffset(offset - (valuePart.length() - valuePart.lastIndexOf('.')) + 1);
        }
   
    }
    
    /**
     * Provides code completion for a comma-separated list of imports context
     */
    @Deprecated
    public static class PackageListSupport extends ClassNameSupport {
        
        public PackageListSupport(boolean tag, String longName, String attrName) {
            super(tag, longName, attrName);
        }
        
        @Override protected String getFakedClassBody(String prefix){
            int commaPos = prefix.lastIndexOf(",");
            
            if (commaPos > -1){
                prefix = prefix.substring(commaPos + 1);
            }
            
            return "import " + prefix; //NOI18N
        }
    }
    
    public static class GetSetPropertyName extends AttributeValueSupport.Default {
        
        public GetSetPropertyName(boolean tag, String longName, String attrName) {
            super(tag, longName, attrName);
        }
        
        @Override
        protected List<String> possibleValues(JspSyntaxSupport sup, SyntaxElement.TagDirective item) {
            ArrayList<String> list = new ArrayList<String>();
            PageInfo.BeanData[] beanData = sup.getBeanData();
            if(beanData != null) {
                for (int i = 0; i < beanData.length; i++) {
                    list.add(beanData[i].getId());
                }
            }
            return list;
        }
        
    }
    
    
    public abstract static class GetSetPropertyProperty extends AttributeValueSupport.Default {
        
        public GetSetPropertyProperty(boolean tag, String longName, String attrName) {
            super(tag, longName, attrName);
        }
        
        protected List<String> possibleValues(JspSyntaxSupport sup, SyntaxElement.TagDirective item, boolean setter) {
            ArrayList<String> list = new ArrayList<String>();
            String namePropertyValue = (String)item.getAttributes().get("name");    // NOI18N
            if (namePropertyValue != null) {
                String className = null;
                PageInfo.BeanData[] beanData = sup.getBeanData();
                for (int i = 0; i < beanData.length; i++) {
                    if (beanData[i] == null || beanData[i].getId() == null)
                        continue;
                    
                    if (beanData[i].getId().equals(namePropertyValue)) {
                        className = beanData[i].getClassName();
                        break;
                    }
                }
                
                if (className != null) {
                    ClassLoader cld = null;
                    try {
                        FileObject fileObject = NbEditorUtilities.getDataObject( sup.getDocument()).getPrimaryFile();
                        cld = JspUtils.getModuleClassLoader(fileObject);
                        Class beanClass = Class.forName(className, false, cld);
                        Introspector.flushFromCaches(beanClass);
                        BeanInfo benInfo = Introspector.getBeanInfo(beanClass);
                        PropertyDescriptor[] properties = benInfo.getPropertyDescriptors();
                        for (int j = 0; j < properties.length; j++) {
                            if (setter && (properties[j].getWriteMethod() != null))
                                list.add(properties[j].getName());
                            if (!setter && (properties[j].getReadMethod() != null) && !properties[j].getName().equals("class")) //NOI18N
                                list.add(properties[j].getName());
                        }
                    } catch (ClassNotFoundException e) {
                        //do nothing
                    } catch (IntrospectionException e) {
                        //do nothing
                    } finally {
                        // avoids JAR locking
                        if (cld instanceof Closeable) {
                            try {
                                ((Closeable) cld).close();
                            } catch (IOException ex) {
                                Logger.getLogger(AttrSupports.class.getName()).log(Level.INFO, null, ex);
                            }
                        }
                    }
                }
            }
            return list;
        }
    }
    
    public static class GetPropertyProperty extends GetSetPropertyProperty {
        
        public GetPropertyProperty() {
            super(true, "jsp:getProperty", "property");     // NOI18N
        }
        
        @Override
        public List<String> possibleValues(JspSyntaxSupport sup, SyntaxElement.TagDirective item) {
            return possibleValues(sup, item, false);
        }
        
    }
    
    public static class SetPropertyProperty extends GetSetPropertyProperty {
        
        public SetPropertyProperty() {
            super(true, "jsp:setProperty", "property"); // NOI18N
        }
        
        @Override
        protected List<String> possibleValues(JspSyntaxSupport sup, SyntaxElement.TagDirective item) {
            List<String> list = possibleValues(sup, item, true);
            list.add(0, "*");  // NOI18N
            return list;
        }
        
    }
    
    public static class TaglibURI extends AttributeValueSupport.Default {
        
        public TaglibURI() {
            super(false, "taglib", "uri");      // NOI18N
        }
        
        @Override
        protected List<String> possibleValues(JspSyntaxSupport sup, SyntaxElement.TagDirective item) {
            List<String> list = new ArrayList<String>();
            Map map = sup.getTagLibraryMappings();
            if (map != null) {
                Iterator iterator = map.keySet().iterator();
                while(iterator.hasNext()) {
                    String s = (String)iterator.next();
                    list.add(s);
                }
            }
            // sort alphabetically
            Collections.sort(list);
            return list;
        }
        
    }
    
    public static class TaglibTagdir extends AttributeValueSupport.Default {
        
        public TaglibTagdir() {
            super(false, "taglib", "tagdir");      // NOI18N
        }
        
        @Override
        protected List<String> possibleValues(JspSyntaxSupport sup, SyntaxElement.TagDirective item) {
            List<String> l = new ArrayList<String>();
            FileObject orig = sup.getFileObject();
            FileObject documentBase = JspUtils.guessWebModuleRoot(orig);
            if (documentBase != null) {
                FileObject webInfTags = JspUtils.findRelativeFileObject(documentBase, "WEB-INF/tags");
                if (webInfTags != null) {
                    // WEB-INF/tags itself
                    if (isValidTagDir(webInfTags)) {
                        l.add(JspUtils.findRelativeContextPath(documentBase, webInfTags));
                    }
                    // subfolders of WEB-INF/tags
                    Enumeration en = webInfTags.getFolders(true);
                    while (en.hasMoreElements()) {
                        FileObject subF = (FileObject)en.nextElement();
                        if (isValidTagDir(subF)) {
                            l.add(JspUtils.findRelativeContextPath(documentBase, subF));
                        }
                    }
                }
            }
            // sort alphabetically
            Collections.sort(l);
            return l;
        }
        
        private boolean isValidTagDir(FileObject subF) {
            // must contain at least one file
            return subF.getChildren(false).hasMoreElements();
        }
        
    }
    
    
    /** Support for code completing of package and class. */
    public static class FilenameSupport extends AttributeValueSupport.Default {
        static final ImageIcon PACKAGE_ICON =
                ImageUtilities.loadImageIcon("org/openide/loaders/defaultFolder.gif", false); // NOI18N
      
        public FilenameSupport(boolean tag, String longName, String attrName) {
            super(tag, longName, attrName);
        }
        
        /** Returns the complete result that contains elements from getCompletionItems.   */
        @Override
        public void result(JspCompletionQuery.CompletionResultSet result, JTextComponent component, int offset,
                JspSyntaxSupport sup, SyntaxElement.TagDirective item, String valuePart) {
            String path = "";   // NOI18N
            String fileNamePart = valuePart;
            int lastSlash = valuePart.lastIndexOf('/');
            if (lastSlash == 0) {
                path = "/"; // NOI18N
                fileNamePart = valuePart.substring(1);
            } else if (lastSlash > 0) { // not a leading slash?
                path = valuePart.substring(0, lastSlash);
                fileNamePart = (lastSlash == valuePart.length())? "": valuePart.substring(lastSlash+1);    // NOI18N
            }
            
            int anchor = offset - valuePart.length() + lastSlash + 1;  // works even with -1
            
            try {
                FileObject orig = sup.getFileObject();
                FileObject documentBase = JspUtils.guessWebModuleRoot(orig);
                // need to normalize fileNamePart with respect to orig
                String ctxPath = JspUtils.resolveRelativeURL("/"+orig.getPath(), path);  // NOI18N
                //is this absolute path?
                if (path.startsWith("/") && documentBase != null) {
                    ctxPath = documentBase.getPath() + path;
                } else {
                    ctxPath = ctxPath.substring(1);
                }

                FileSystem fs = orig.getFileSystem();
                
                FileObject folder = fs.findResource(ctxPath);
                if (folder != null) {
                    //add all accessible files from current context
                    result.addAllItems(files(anchor, folder, fileNamePart, sup));
                    
                    //add go up in the directories structure item
                    if (!folder.equals(documentBase) && !path.startsWith("/") // NOI18N
                            && (path.length() == 0 || (path.lastIndexOf("../")+3 == path.length()))){ // NOI18N
                        result.addItem(JspCompletionItem.createGoUpFileCompletionItem(anchor, java.awt.Color.BLUE, PACKAGE_ICON)); // NOI18N
                    }
                }
            } catch (FileStateInvalidException ex) {
                // unreachable FS - disable completion
            } catch (IllegalArgumentException ex) {
                // resolving failed
            }
            
            result.setAnchorOffset(anchor);
        }
        
        private List<JspCompletionItem> files(int offset, FileObject folder, String prefix, JspSyntaxSupport sup) {
            ArrayList<JspCompletionItem> res = new ArrayList<JspCompletionItem>();
            TreeMap<String, JspCompletionItem> resFolders = new TreeMap<String, JspCompletionItem>();
            TreeMap<String, JspCompletionItem> resFiles = new TreeMap<String, JspCompletionItem>();
            
            Enumeration<? extends FileObject> files = folder.getChildren(false);
            while (files.hasMoreElements()) {
                FileObject file = files.nextElement();
                String fname = file.getNameExt();
                if (fname.startsWith(prefix) && !"cvs".equalsIgnoreCase(fname)) {
                    
                    if (file.isFolder())
                        resFolders.put(file.getNameExt(), JspCompletionItem.createFileCompletionItem(file.getNameExt() + "/", offset, java.awt.Color.BLUE, PACKAGE_ICON));
                    else{
                        java.awt.Image icon = JspUtils.getIcon(file);
                        if (icon != null)
                            resFiles.put(file.getNameExt(), JspCompletionItem.createFileCompletionItem(file.getNameExt(), offset, java.awt.Color.BLACK, new javax.swing.ImageIcon(icon)));
                        else
                            resFiles.put(file.getNameExt(), JspCompletionItem.createFileCompletionItem(file.getNameExt(), offset, java.awt.Color.BLACK, null));
                    }
                }
            }
            res.addAll(resFolders.values());
            res.addAll(resFiles.values());
            
            return res;
        }
        
    }
    
    public static class TrueFalseSupport extends AttributeValueSupport.Default {
        
        public TrueFalseSupport(boolean tag, String longName, String attrName) {
            super(tag, longName, attrName);
        }
        
        @Override
        protected List<String> possibleValues(JspSyntaxSupport sup, SyntaxElement.TagDirective item) {
            ArrayList<String> list = new ArrayList<String>();
            list.add("false");   // NOI18N
            list.add("true");    // NOI18N
            return list;
        }
        
    }
    
    public static class PageLanguage extends AttributeValueSupport.Default {
        
        public PageLanguage() {
            super(false, "page", "language");    // NOI18N
        }
        
        @Override
        protected List<String> possibleValues(JspSyntaxSupport sup, SyntaxElement.TagDirective item) {
            ArrayList<String> list = new ArrayList<String>();
            list.add("java");    // NOI18N
            return list;
        }
        
    }
    
    public static class EncodingSupport extends AttributeValueSupport.Default {
        
        public EncodingSupport(boolean tag, String longName, String attrName) {
            super(tag, longName, attrName);
        }
        
        @Override
        protected List<String> possibleValues(JspSyntaxSupport sup, SyntaxElement.TagDirective item) {
            ArrayList<String> list = new ArrayList<String>();
            Iterator<String> iter = java.nio.charset.Charset.availableCharsets().keySet().iterator();
            
            while (iter.hasNext())
                list.add(iter.next());
            
            return list;
        }
        
    }
    
}
