/*
 * Copyright 2019 junichi11.
 *
 * Licensed 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.php.cake3.ui.actions.gotos.status;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import org.netbeans.modules.csl.spi.ParserResult;
import org.netbeans.modules.parsing.api.ParserManager;
import org.netbeans.modules.parsing.api.ResultIterator;
import org.netbeans.modules.parsing.api.Source;
import org.netbeans.modules.parsing.api.UserTask;
import org.netbeans.modules.parsing.spi.ParseException;
import org.netbeans.modules.php.api.phpmodule.PhpModule;
import org.netbeans.modules.php.api.util.FileUtils;
import org.netbeans.modules.php.api.util.StringUtils;
import org.netbeans.modules.php.cake3.modules.CakePHP3Module;
import org.netbeans.modules.php.cake3.modules.CakePHP3Module.Category;
import org.netbeans.modules.php.cake3.modules.ModuleInfo;
import org.netbeans.modules.php.cake3.modules.ModuleUtils;
import org.netbeans.modules.php.cake3.ui.actions.gotos.items.GoToItem;
import org.netbeans.modules.php.cake3.ui.actions.gotos.items.GoToItemFactory;
import org.netbeans.modules.php.editor.api.ElementQuery;
import org.netbeans.modules.php.editor.api.ElementQueryFactory;
import org.netbeans.modules.php.editor.api.NameKind;
import org.netbeans.modules.php.editor.api.QuerySupportFactory;
import org.netbeans.modules.php.editor.api.elements.ClassElement;
import org.netbeans.modules.php.editor.parser.PHPParseResult;
import org.netbeans.modules.php.editor.parser.astnodes.visitors.DefaultVisitor;
import org.openide.filesystems.FileObject;

/**
 *
 * @author junichi11
 */
public abstract class CakePHP3GoToStatus {

    private final FileObject fileObject;
    private final int offset;
    static final int DEFAULT_OFFSET = 0;

    CakePHP3GoToStatus(FileObject fileObject, int offset) {
        this.fileObject = fileObject;
        this.offset = offset;
    }

    public FileObject getFileObject() {
        return fileObject;
    }

    public int getOffset() {
        return offset;
    }

    public void scan() {
        if (fileObject == null) {
            return;
        }
        PhpModule phpModule = getPhpModule();
        if (phpModule == null) {
            return;
        }
        scan(phpModule, getFileObject(), offset);
    }

    public List<GoToItem> getAllItems() {
        List<GoToItem> items = new ArrayList<>();
        items.addAll(getControllers());
        items.addAll(getTables());
        items.addAll(getEntities());
        items.addAll(getTemplates());
        items.addAll(getComponents());
        items.addAll(getHelpers());
        items.addAll(getBehaviors());
        items.addAll(getTestCases());
        items.addAll(getFixtures());
        return items;
    }

    public List<GoToItem> getControllers() {
        return Collections.emptyList();
    }

    /**
     * Get all controllers for the base directory.
     *
     * @return
     */
    public List<GoToItem> getAllControllers() {
        return createAllItems(Category.CONTROLLER);
    }

    public List<GoToItem> getTables() {
        return Collections.emptyList();
    }

    public List<GoToItem> getAllTables() {
        return createAllItems(Category.TABLE);
    }

    public List<GoToItem> getEntities() {
        CakePHP3Module cakeModule = CakePHP3Module.forFileObject(getFileObject());
        List<GoToItem> items = new ArrayList<>();
        for (GoToItem item : getTables()) {
            FileObject table = item.getFileObject();
            FileObject file = cakeModule.getEntity(table);
            if (file != null) {
                items.add(GoToItemFactory.create(Category.ENTITY, file, DEFAULT_OFFSET));
            }
        }
        return items;
    }

    public List<GoToItem> getAllEntities() {
        return createAllItems(Category.ENTITY);
    }

    public List<GoToItem> getTemplates() {
        return Collections.emptyList();
    }

    public List<GoToItem> getComponents() {
        return Collections.emptyList();
    }

    public List<GoToItem> getAllComponents() {
        return createAllItems(Category.COMPONENT);
    }

    public List<GoToItem> getHelpers() {
        return Collections.emptyList();
    }

    public List<GoToItem> getAllHelpers() {
        return createAllItems(Category.HELPER);
    }

    public List<GoToItem> getBehaviors() {
        return Collections.emptyList();
    }

    public List<GoToItem> getAllBehaviors() {
        return createAllItems(Category.BEHAVIOR);
    }

    public List<GoToItem> getTestCases() {
        if (fileObject == null || !FileUtils.isPhpFile(fileObject)) {
            return Collections.emptyList();
        }
        CakePHP3Module cakeModule = CakePHP3Module.forFileObject(fileObject);
        ModuleInfo info = cakeModule.createModuleInfo(fileObject);
        if (ModuleUtils.isTemplate(info.getCategory())) {
            return Collections.emptyList();
        }

        List<GoToItem> items = new ArrayList<>();
        List<FileObject> directories = cakeModule.getDirectories(info.getBase(), null, info.getPluginName());
        for (FileObject directory : directories) {
            Set<ClassElement> classElements = getClassElements(directory, fileObject.getName() + "Test"); // NOI18N
            for (ClassElement classElement : classElements) {
                FileObject testcase = classElement.getFileObject();
                items.add(GoToItemFactory.create(Category.TEST_CASE, testcase, DEFAULT_OFFSET));
            }
        }
        return items;
    }

    public List<GoToItem> getAllTestCases() {
        return createAllItems(Category.TEST_CASE);
    }

    public List<GoToItem> getFixtures() {
        return Collections.emptyList();
    }

    public List<GoToItem> getAllFixtures() {
        return createAllItems(Category.FIXTURE);
    }

    public List<GoToItem> getImportants() {
        return createAllItems(Category.CONFIG);
    }

    public List<GoToItem> getViewCells() {
        return Collections.emptyList();
    }

    public List<GoToItem> getAllViewCells() {
        return createAllItems(Category.VIEW_CELL);
    }

    protected List<GoToItem> createAllItems(Category category) {
        if (fileObject == null) {
            return Collections.emptyList();
        }
        List<GoToItem> items = new ArrayList<>();
        CakePHP3Module cakeModule = CakePHP3Module.forFileObject(fileObject);
        ModuleInfo info = cakeModule.createModuleInfo(fileObject);
        List<FileObject> directories = cakeModule.getDirectories(info.getBase(), category, info.getPluginName());
        for (FileObject directory : directories) {
            for (FileObject child : directory.getChildren()) {
                if (child.isFolder() || !FileUtils.PHP_MIME_TYPE.equals(child.getMIMEType(FileUtils.PHP_MIME_TYPE))) {
                    continue;
                }
                items.add(GoToItemFactory.create(category, child, 0));
            }
        }
        return items;
    }

    /**
     * Get class elements.
     *
     * @param targetDirectory
     * @param targetName class name
     * @return class elements
     */
    public Set<ClassElement> getClassElements(FileObject targetDirectory, String targetName) {
        if (targetDirectory == null || !targetDirectory.isFolder() || StringUtils.isEmpty(targetName)) {
            return Collections.emptySet();
        }

        ElementQuery.Index indexQuery = ElementQueryFactory.createIndexQuery(QuerySupportFactory.get(targetDirectory));
        return indexQuery.getClasses(NameKind.prefix(targetName));
    }

    private PhpModule getPhpModule() {
        if (fileObject == null) {
            return null;
        }
        return PhpModule.Factory.forFileObject(fileObject);
    }

    protected void scan(final DefaultVisitor visitor, FileObject targetFile) throws ParseException {
        ParserManager.parse(Collections.singleton(Source.create(targetFile)), new UserTask() {
            @Override
            public void run(ResultIterator resultIterator) throws Exception {
                ParserResult result = (ParserResult) resultIterator.getParserResult();
                if (result == null) {
                    return;
                }
                if (result instanceof PHPParseResult) {
                    PHPParseResult pr = (PHPParseResult) result;
                    pr.getProgram().accept(visitor);
                }
            }
        });
    }

    /**
     * Scan a file.
     *
     * @param phpModule
     * @param fileObject
     * @param offset
     */
    protected abstract void scan(PhpModule phpModule, FileObject fileObject, int offset);

    /**
     * Get GoToItems for the caret position.
     *
     * @return GoToItems
     */
    public abstract List<GoToItem> getSmart();

}