/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * 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 com.android.ide.common.resources;

import com.android.ide.common.rendering.api.DensityBasedResourceValue;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.resources.ValueResourceParser.IValueResourceRepository;
import com.android.ide.common.resources.configuration.DensityQualifier;
import com.android.ide.common.resources.configuration.ResourceQualifier;
import com.android.io.IAbstractFile;
import com.android.io.StreamException;
import com.android.resources.ResourceType;

import java.io.IOException;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * Represents a resource file that also generates ID resources.
 * <p>
 * This is typically an XML file in res/layout or res/menu
 */
public final class IdGeneratingResourceFile extends ResourceFile
                                            implements IValueResourceRepository {

    private final Map<String, ResourceValue> mIdResources =
        new HashMap<String, ResourceValue>();

    private final Collection<ResourceType> mResourceTypeList;

    private final String mFileName;

    private final ResourceType mFileType;

    private final ResourceValue mFileValue;

    public IdGeneratingResourceFile(IAbstractFile file, ResourceFolder folder, ResourceType type) {
        super(file, folder);

        mFileType = type;

        // Set up our resource types
        mResourceTypeList = EnumSet.of(mFileType, ResourceType.ID);

        // compute the resource name
        mFileName = getFileName(type);

        // Get the resource value of this file as a whole layout
        mFileValue = getFileValue(file, folder);
    }

    @Override
    protected void load(ScanningContext context) {
        // Parse the file and look for @+id/ entries
        parseFileForIds(context);

        // create the resource items in the repository
        updateResourceItems(context);
    }

    @Override
    protected void update(ScanningContext context) {
        // Copy the previous list of ID names
        Set<String> oldIdNames = new HashSet<String>(mIdResources.keySet());

        // reset current content.
        mIdResources.clear();

        // need to parse the file and find the IDs.
        if (!parseFileForIds(context)) {
            context.requestFullAapt();
            // Continue through to updating the resource item here since it
            // will make for example layout rendering more accurate until
            // aapt is re-run
        }

        // We only need to update the repository if our IDs have changed
        Set<String> keySet = mIdResources.keySet();
        assert keySet != oldIdNames;
        if (oldIdNames.equals(keySet) == false) {
            updateResourceItems(context);
        }
    }

    @Override
    protected void dispose(ScanningContext context) {
        ResourceRepository repository = getRepository();

        // Remove declarations from this file from the repository
        repository.removeFile(mResourceTypeList, this);

        // Ask for an ID refresh since we'll be taking away ID generating items
        context.requestFullAapt();
    }

    @Override
    public Collection<ResourceType> getResourceTypes() {
        return mResourceTypeList;
    }

    @Override
    public boolean hasResources(ResourceType type) {
        return (type == mFileType) || (type == ResourceType.ID && !mIdResources.isEmpty());
    }

    @Override
    public ResourceValue getValue(ResourceType type, String name) {
        // Check to see if they're asking for one of the right types:
        if (type != mFileType && type != ResourceType.ID) {
            return null;
        }

        // If they're looking for a resource of this type with this name give them the whole file
        if (type == mFileType && name.equals(mFileName)) {
            return mFileValue;
        } else {
            // Otherwise try to return them an ID
            // the map will return null if it's not found
            return mIdResources.get(name);
        }
    }

    /**
     * Looks through the file represented for Ids and adds them to
     * our id repository
     *
     * @return true if parsing succeeds and false if it fails
     */
    private boolean parseFileForIds(ScanningContext context) {
        IdResourceParser parser = new IdResourceParser(this, context, isFramework());
        try {
            IAbstractFile file = getFile();
            return parser.parse(mFileType, file.getOsLocation(), file.getContents());
        } catch (IOException e) {
            // Pass
        } catch (StreamException e) {
            // Pass
        }

        return false;
    }

    /**
     * Add the resources represented by this file to the repository
     */
    private void updateResourceItems(ScanningContext context) {
        ResourceRepository repository = getRepository();

        // remove this file from all existing ResourceItem.
        repository.removeFile(mResourceTypeList, this);

        // First add this as a layout file
        ResourceItem item = repository.getResourceItem(mFileType, mFileName);
        item.add(this);

        // Now iterate through our IDs and add
        for (String idName : mIdResources.keySet()) {
            item = repository.getResourceItem(ResourceType.ID, idName);
            // add this file to the list of files generating ID resources.
            item.add(this);
        }

        //  Ask the repository for an ID refresh
        context.requestFullAapt();
    }

    /**
     * Returns the resource value associated with this whole file as a layout resource
     * @param file the file handler that represents this file
     * @param folder the folder this file is under
     * @return a resource value associated with this layout
     */
    private ResourceValue getFileValue(IAbstractFile file, ResourceFolder folder) {
        // test if there's a density qualifier associated with the resource
        DensityQualifier qualifier = folder.getConfiguration().getDensityQualifier();

        ResourceValue value;
        if (!ResourceQualifier.isValid(qualifier)) {
            value = new ResourceValue(mFileType, mFileName,
                    file.getOsLocation(), isFramework());
        } else {
            value = new DensityBasedResourceValue(
                    mFileType, mFileName,
                    file.getOsLocation(),
                    qualifier.getValue(),
                    isFramework());
        }
        return value;
    }


    /**
     * Returns the name of this resource.
     */
    private String getFileName(ResourceType type) {
        // get the name from the filename.
        String name = getFile().getName();

        int pos = name.indexOf('.');
        if (pos != -1) {
            name = name.substring(0, pos);
        }

        return name;
    }

    @Override
    public void addResourceValue(ResourceValue value) {
        // Just overwrite collisions. We're only interested in the unique
        // IDs declared
        mIdResources.put(value.getName(), value);
    }

    @Override
    public boolean hasResourceValue(ResourceType type, String name) {
        if (type == ResourceType.ID) {
            return mIdResources.containsKey(name);
        }

        return false;
    }
}
