/*
 * Copyright (C) 2009 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.sdk;

import com.android.ide.common.resources.configuration.CountryCodeQualifier;
import com.android.ide.common.resources.configuration.DensityQualifier;
import com.android.ide.common.resources.configuration.FolderConfiguration;
import com.android.ide.common.resources.configuration.KeyboardStateQualifier;
import com.android.ide.common.resources.configuration.NavigationMethodQualifier;
import com.android.ide.common.resources.configuration.NavigationStateQualifier;
import com.android.ide.common.resources.configuration.NetworkCodeQualifier;
import com.android.ide.common.resources.configuration.ScreenDimensionQualifier;
import com.android.ide.common.resources.configuration.ScreenOrientationQualifier;
import com.android.ide.common.resources.configuration.ScreenRatioQualifier;
import com.android.ide.common.resources.configuration.ScreenSizeQualifier;
import com.android.ide.common.resources.configuration.TextInputMethodQualifier;
import com.android.ide.common.resources.configuration.TouchScreenQualifier;

import org.w3c.dom.Document;
import org.w3c.dom.Element;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * Class representing a layout device.
 *
 * A Layout device is a collection of {@link FolderConfiguration} that can be used to render Android
 * layout files.
 *
 * It also contains a single xdpi/ydpi that is independent of the {@link FolderConfiguration}.
 *
 * If the device is meant to represent a true device, then most of the FolderConfigurations' content
 * should be identical, with only a few qualifiers (orientation, keyboard state) that would differ.
 * However it is simpler to reuse the FolderConfiguration class (with the non changing qualifiers
 * duplicated in each configuration) as it's what's being used by the rendering library.
 *
 * To create, edit and delete LayoutDevice objects, see {@link LayoutDeviceManager}.
 * The class is not technically immutable but behaves as such outside of its package.
 */
public class LayoutDevice {

    private final String mName;

    /**
     * Wrapper around a {@link FolderConfiguration}.
     * <p/>This adds a name, accessible through {@link #getName()}.
     * <p/>The folder config can be accessed through {@link #getConfig()}.
     *
     */
    public final static class DeviceConfig {
        private final String mName;
        private final FolderConfiguration mConfig;

        DeviceConfig(String name, FolderConfiguration config) {
            mName = name;
            mConfig = config;
        }

        public String getName() {
            return mName;
        }

        public FolderConfiguration getConfig() {
            return mConfig;
        }
    }

    /** editable list of the config */
    private final ArrayList<DeviceConfig> mConfigs = new ArrayList<DeviceConfig>();
    /** Read-only list */
    private List<DeviceConfig> mROList;

    private float mXDpi = Float.NaN;
    private float mYDpi = Float.NaN;

    LayoutDevice(String name) {
        mName = name;
    }

    /**
     * Saves the Layout Device into a document under a given node
     * @param doc the document.
     * @param parentNode the parent node.
     */
    void saveTo(Document doc, Element parentNode) {
        // create the device node
        Element deviceNode = createNode(doc, parentNode, LayoutDevicesXsd.NODE_DEVICE);

        // create the name attribute (no namespace on this one).
        deviceNode.setAttribute(LayoutDevicesXsd.ATTR_NAME, mName);

        // create a default with the x/y dpi
        Element defaultNode = createNode(doc, deviceNode, LayoutDevicesXsd.NODE_DEFAULT);
        if (Float.isNaN(mXDpi) == false) {
            Element xdpiNode = createNode(doc, defaultNode, LayoutDevicesXsd.NODE_XDPI);
            xdpiNode.setTextContent(Float.toString(mXDpi));
        }
        if (Float.isNaN(mYDpi) == false) {
            Element xdpiNode = createNode(doc, defaultNode, LayoutDevicesXsd.NODE_YDPI);
            xdpiNode.setTextContent(Float.toString(mYDpi));
        }

        // then save all the configs.
        synchronized (mConfigs) {
            for (DeviceConfig config : mConfigs) {
                saveConfigTo(doc, deviceNode, config.getName(), config.getConfig());
            }
        }
    }

    /**
     * Creates and returns a new NS-enabled node.
     * @param doc the {@link Document}
     * @param parentNode the parent node. The new node is appended to this one as a child.
     * @param name the name of the node.
     * @return the newly created node.
     */
    private Element createNode(Document doc, Element parentNode, String name) {
        Element newNode = doc.createElementNS(
                LayoutDevicesXsd.NS_LAYOUT_DEVICE_XSD, name);
        newNode.setPrefix(doc.lookupPrefix(LayoutDevicesXsd.NS_LAYOUT_DEVICE_XSD));
        parentNode.appendChild(newNode);

        return newNode;
    }

    /**
     * Saves a {@link FolderConfiguration} in a {@link Document}.
     * @param doc the Document in which to save
     * @param parent the parent node
     * @param configName the name of the config
     * @param config the config to save
     */
    private void saveConfigTo(Document doc, Element parent, String configName,
            FolderConfiguration config) {
        Element configNode = createNode(doc, parent, LayoutDevicesXsd.NODE_CONFIG);

        // create the name attribute (no namespace on this one).
        configNode.setAttribute(LayoutDevicesXsd.ATTR_NAME, configName);

        // now do the qualifiers
        CountryCodeQualifier ccq = config.getCountryCodeQualifier();
        if (ccq != null) {
            Element node = createNode(doc, configNode, LayoutDevicesXsd.NODE_COUNTRY_CODE);
            node.setTextContent(Integer.toString(ccq.getCode()));
        }

        NetworkCodeQualifier ncq = config.getNetworkCodeQualifier();
        if (ncq != null) {
            Element node = createNode(doc, configNode, LayoutDevicesXsd.NODE_NETWORK_CODE);
            node.setTextContent(Integer.toString(ncq.getCode()));
        }

        ScreenSizeQualifier slsq = config.getScreenSizeQualifier();
        if (slsq != null) {
            Element node = createNode(doc, configNode, LayoutDevicesXsd.NODE_SCREEN_SIZE);
            node.setTextContent(slsq.getFolderSegment());
        }

        ScreenRatioQualifier srq = config.getScreenRatioQualifier();
        if (srq != null) {
            Element node = createNode(doc, configNode, LayoutDevicesXsd.NODE_SCREEN_RATIO);
            node.setTextContent(srq.getFolderSegment());
        }

        ScreenOrientationQualifier soq = config.getScreenOrientationQualifier();
        if (soq != null) {
            Element node = createNode(doc, configNode, LayoutDevicesXsd.NODE_SCREEN_ORIENTATION);
            node.setTextContent(soq.getFolderSegment());
        }

        DensityQualifier dq = config.getDensityQualifier();
        if (dq != null) {
            Element node = createNode(doc, configNode, LayoutDevicesXsd.NODE_PIXEL_DENSITY);
            node.setTextContent(dq.getFolderSegment());
        }

        TouchScreenQualifier ttq = config.getTouchTypeQualifier();
        if (ttq != null) {
            Element node = createNode(doc, configNode, LayoutDevicesXsd.NODE_TOUCH_TYPE);
            node.setTextContent(ttq.getFolderSegment());
        }

        KeyboardStateQualifier ksq = config.getKeyboardStateQualifier();
        if (ksq != null) {
            Element node = createNode(doc, configNode, LayoutDevicesXsd.NODE_KEYBOARD_STATE);
            node.setTextContent(ksq.getFolderSegment());
        }

        TextInputMethodQualifier timq = config.getTextInputMethodQualifier();
        if (timq != null) {
            Element node = createNode(doc, configNode, LayoutDevicesXsd.NODE_TEXT_INPUT_METHOD);
            node.setTextContent(timq.getFolderSegment());
        }

        NavigationStateQualifier nsq = config.getNavigationStateQualifier();
        if (nsq != null) {
            Element node = createNode(doc, configNode, LayoutDevicesXsd.NODE_NAV_STATE);
            node.setTextContent(nsq.getFolderSegment());
        }

        NavigationMethodQualifier nmq = config.getNavigationMethodQualifier();
        if (nmq != null) {
            Element node = createNode(doc, configNode, LayoutDevicesXsd.NODE_NAV_METHOD);
            node.setTextContent(nmq.getFolderSegment());
        }

        ScreenDimensionQualifier sdq = config.getScreenDimensionQualifier();
        if (sdq != null) {
            Element sizeNode = createNode(doc, configNode, LayoutDevicesXsd.NODE_SCREEN_DIMENSION);

            Element node = createNode(doc, sizeNode, LayoutDevicesXsd.NODE_SIZE);
            node.setTextContent(Integer.toString(sdq.getValue1()));

            node = createNode(doc, sizeNode, LayoutDevicesXsd.NODE_SIZE);
            node.setTextContent(Integer.toString(sdq.getValue2()));
        }
    }

    /**
     * Adds config to the LayoutDevice.
     * <p/>This ensures that no two configurations have the same. If a config already exists
     * with the same name, the new config replaces it.
     *
     * @param name the name of the config.
     * @param config the config.
     */
    void addConfig(String name, FolderConfiguration config) {
        synchronized (mConfigs) {
            doAddConfig(name, config);
            seal();
        }
    }

    /**
     * Adds a list of config to the LayoutDevice
     * <p/>This ensures that no two configurations have the same. If a config already exists
     * with the same name, the new config replaces it.

     * @param configs the configs to add.
     */
    void addConfigs(List<DeviceConfig> configs) {
        synchronized (mConfigs) {
            // add the configs manually one by one, to check for no duplicate.
            for (DeviceConfig config : configs) {
                String name = config.getName();

                for (DeviceConfig c : mConfigs) {
                    if (c.getName().equals(name)) {
                        mConfigs.remove(c);
                        break;
                    }
                }

                mConfigs.add(config);
            }

            seal();
        }
    }

    /**
     * Removes a config by its name.
     * @param name the name of the config to remove.
     */
    void removeConfig(String name) {
        synchronized (mConfigs) {
            for (DeviceConfig config : mConfigs) {
                if (config.getName().equals(name)) {
                    mConfigs.remove(config);
                    seal();
                    return;
                }
            }
        }
    }

    /**
     * Adds config to the LayoutDevice. This is to be used to add plenty of
     * configurations. It must be followed by {@link #_seal()}.
     * <p/>This ensures that no two configurations have the same. If a config already exists
     * with the same name, the new config replaces it.
     * <p/><strong>This must be called inside a <code>synchronized(mConfigs)</code> block.</strong>
     *
     * @param name the name of the config
     * @param config the config.
     */
    private void doAddConfig(String name, FolderConfiguration config) {
        // remove config that would have the same name to ensure no duplicate
        for (DeviceConfig c : mConfigs) {
            if (c.getName().equals(name)) {
                mConfigs.remove(c);
                break;
            }
        }
        mConfigs.add(new DeviceConfig(name, config));
    }

    /**
     * Seals the layout device by setting up {@link #mROList}.
     * <p/><strong>This must be called inside a <code>synchronized(mConfigs)</code> block.</strong>
     */
    private void seal() {
        mROList = Collections.unmodifiableList(mConfigs);
    }

    void setXDpi(float xdpi) {
        mXDpi = xdpi;
    }

    void setYDpi(float ydpi) {
        mYDpi = ydpi;
    }

    public String getName() {
        return mName;
    }

    /**
     * Returns an unmodifiable list of all the {@link DeviceConfig}.
     */
    public List<DeviceConfig> getConfigs() {
        synchronized (mConfigs) {
            return mROList;
        }
    }

    /**
     * Returns a {@link DeviceConfig} by its name.
     */
    public DeviceConfig getDeviceConfigByName(String name) {
        synchronized (mConfigs) {
            for (DeviceConfig config : mConfigs) {
                if (config.getName().equals(name)) {
                    return config;
                }
            }
        }

        return null;
    }

    /**
     * Returns a {@link FolderConfiguration} by its name.
     */
    public FolderConfiguration getFolderConfigByName(String name) {
        synchronized (mConfigs) {
            for (DeviceConfig config : mConfigs) {
                if (config.getName().equals(name)) {
                    return config.getConfig();
                }
            }
        }

        return null;
    }

    /**
     * Returns the dpi of the Device screen in X.
     * @return the dpi of screen or {@link Float#NaN} if it's not set.
     */
    public float getXDpi() {
        return mXDpi;
    }

    /**
     * Returns the dpi of the Device screen in Y.
     * @return the dpi of screen or {@link Float#NaN} if it's not set.
     */
    public float getYDpi() {
        return mYDpi;
    }
 }
