/*
 * Copyright (C) 2011 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.common.layout.relative;

import static com.android.ide.common.api.SegmentType.BASELINE;
import static com.android.ide.common.api.SegmentType.BOTTOM;
import static com.android.ide.common.api.SegmentType.CENTER_HORIZONTAL;
import static com.android.ide.common.api.SegmentType.CENTER_VERTICAL;
import static com.android.ide.common.api.SegmentType.LEFT;
import static com.android.ide.common.api.SegmentType.RIGHT;
import static com.android.ide.common.api.SegmentType.TOP;
import static com.android.ide.common.api.SegmentType.UNKNOWN;
import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_ABOVE;
import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_ALIGN_BASELINE;
import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_ALIGN_BOTTOM;
import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_ALIGN_LEFT;
import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_ALIGN_PARENT_BOTTOM;
import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_ALIGN_PARENT_LEFT;
import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_ALIGN_PARENT_RIGHT;
import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_ALIGN_PARENT_TOP;
import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_ALIGN_RIGHT;
import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_ALIGN_TOP;
import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_BELOW;
import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_CENTER_HORIZONTAL;
import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_CENTER_IN_PARENT;
import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_CENTER_VERTICAL;
import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_TO_LEFT_OF;
import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_TO_RIGHT_OF;

import com.android.ide.common.api.SegmentType;

import java.util.HashMap;
import java.util.Map;

/**
 * Each constraint type corresponds to a type of constraint available for the
 * RelativeLayout; for example, {@link #LAYOUT_ABOVE} corresponds to the layout_above constraint.
 */
enum ConstraintType {
    LAYOUT_ABOVE(ATTR_LAYOUT_ABOVE,
            null /* sourceX */, BOTTOM, null /* targetX */, TOP,
            false /* targetParent */, true /* horizontalEdge */, false /* verticalEdge */,
            true /* relativeToMargin */),

    LAYOUT_BELOW(ATTR_LAYOUT_BELOW, null, TOP, null, BOTTOM, false, true, false, true),
    ALIGN_TOP(ATTR_LAYOUT_ALIGN_TOP, null, TOP, null, TOP, false, true, false, false),
    ALIGN_BOTTOM(ATTR_LAYOUT_ALIGN_BOTTOM, null, BOTTOM, null, BOTTOM, false, true, false, false),
    ALIGN_LEFT(ATTR_LAYOUT_ALIGN_LEFT, LEFT, null, LEFT, null, false, false, true, false),
    ALIGN_RIGHT(ATTR_LAYOUT_ALIGN_RIGHT, RIGHT, null, RIGHT, null, false, false, true, false),
    LAYOUT_LEFT_OF(ATTR_LAYOUT_TO_LEFT_OF, RIGHT, null, LEFT, null, false, false, true, true),
    LAYOUT_RIGHT_OF(ATTR_LAYOUT_TO_RIGHT_OF, LEFT, null, RIGHT, null, false, false, true, true),
    ALIGN_PARENT_TOP(ATTR_LAYOUT_ALIGN_PARENT_TOP, null, TOP, null, TOP, true, true, false, false),
    ALIGN_BASELINE(ATTR_LAYOUT_ALIGN_BASELINE, null, BASELINE, null, BASELINE, false, true, false,
            false),
    ALIGN_PARENT_LEFT(ATTR_LAYOUT_ALIGN_PARENT_LEFT, LEFT, null, LEFT, null, true, false, true,
            false),
    ALIGN_PARENT_RIGHT(ATTR_LAYOUT_ALIGN_PARENT_RIGHT, RIGHT, null, RIGHT, null, true, false, true,
            false),
    ALIGN_PARENT_BOTTOM(ATTR_LAYOUT_ALIGN_PARENT_BOTTOM, null, BOTTOM, null, BOTTOM, true, true,
            false, false),
    LAYOUT_CENTER_HORIZONTAL(ATTR_LAYOUT_CENTER_HORIZONTAL, CENTER_VERTICAL, null, CENTER_VERTICAL,
            null, true, true, false, false),
    LAYOUT_CENTER_VERTICAL(ATTR_LAYOUT_CENTER_VERTICAL, null, CENTER_HORIZONTAL, null,
            CENTER_HORIZONTAL, true, false, true, false),
    LAYOUT_CENTER_IN_PARENT(ATTR_LAYOUT_CENTER_IN_PARENT, CENTER_VERTICAL, CENTER_HORIZONTAL,
            CENTER_VERTICAL, CENTER_HORIZONTAL, true, true, true, false);

    private ConstraintType(String name, SegmentType sourceSegmentTypeX,
            SegmentType sourceSegmentTypeY, SegmentType targetSegmentTypeX,
            SegmentType targetSegmentTypeY, boolean targetParent, boolean horizontalEdge,
            boolean verticalEdge, boolean relativeToMargin) {
        assert horizontalEdge || verticalEdge;

        this.name = name;
        this.sourceSegmentTypeX = sourceSegmentTypeX != null ? sourceSegmentTypeX : UNKNOWN;
        this.sourceSegmentTypeY = sourceSegmentTypeY != null ? sourceSegmentTypeY : UNKNOWN;
        this.targetSegmentTypeX = targetSegmentTypeX != null ? targetSegmentTypeX : UNKNOWN;
        this.targetSegmentTypeY = targetSegmentTypeY != null ? targetSegmentTypeY : UNKNOWN;
        this.targetParent = targetParent;
        this.horizontalEdge = horizontalEdge;
        this.verticalEdge = verticalEdge;
        this.relativeToMargin = relativeToMargin;
    }

    /** The attribute name of the constraint */
    public final String name;

    /** The horizontal position of the source of the constraint */
    public final SegmentType sourceSegmentTypeX;

    /** The vertical position of the source of the constraint */
    public final SegmentType sourceSegmentTypeY;

    /** The horizontal position of the target of the constraint */
    public final SegmentType targetSegmentTypeX;

    /** The vertical position of the target of the constraint */
    public final SegmentType targetSegmentTypeY;

    /**
     * If true, the constraint targets the parent layout, otherwise it targets another
     * view
     */
    public final boolean targetParent;

    /** If true, this constraint affects the horizontal dimension */
    public final boolean horizontalEdge;

    /** If true, this constraint affects the vertical dimension */
    public final boolean verticalEdge;

    /**
     * Whether this constraint is relative to the margin bounds of the node rather than
     * the node's actual bounds
     */
    public final boolean relativeToMargin;

    /** Map from attribute name to constraint type */
    private static Map<String, ConstraintType> sNameToType;

    /**
     * Returns the {@link ConstraintType} corresponding to the given attribute name, or
     * null if not found.
     *
     * @param attribute the name of the attribute to look up
     * @return the corresponding {@link ConstraintType}
     */
    public static ConstraintType fromAttribute(String attribute) {
        if (sNameToType == null) {
            ConstraintType[] types = ConstraintType.values();
            Map<String, ConstraintType> map = new HashMap<String, ConstraintType>(types.length);
            for (ConstraintType type : types) {
                map.put(type.name, type);
            }
            sNameToType = map;
        }
        return sNameToType.get(attribute);
    }

    /**
     * Returns true if this constraint type represents a constraint where the target edge
     * is one of the parent edges (actual edge, not center/baseline segments)
     *
     * @return true if the target segment is a parent edge
     */
    public boolean isRelativeToParentEdge() {
        return this == ALIGN_PARENT_LEFT || this == ALIGN_PARENT_RIGHT || this == ALIGN_PARENT_TOP
                || this == ALIGN_PARENT_BOTTOM;
    }

    /**
     * Returns a {@link ConstraintType} for a potential match of edges.
     *
     * @param withParent if true, the target is the parent
     * @param from the source edge
     * @param to the target edge
     * @return a {@link ConstraintType}, or null
     */
    public static ConstraintType forMatch(boolean withParent, SegmentType from, SegmentType to) {
        // Attached to parent edge?
        if (withParent) {
            switch (from) {
                case TOP:
                    return ALIGN_PARENT_TOP;
                case BOTTOM:
                    return ALIGN_PARENT_BOTTOM;
                case LEFT:
                    return ALIGN_PARENT_LEFT;
                case RIGHT:
                    return ALIGN_PARENT_RIGHT;
                case CENTER_HORIZONTAL:
                    return LAYOUT_CENTER_VERTICAL;
                case CENTER_VERTICAL:
                    return LAYOUT_CENTER_HORIZONTAL;
            }

            return null;
        }

        // Attached to some other node.
        switch (from) {
            case TOP:
                switch (to) {
                    case TOP:
                        return ALIGN_TOP;
                    case BOTTOM:
                        return LAYOUT_BELOW;
                    case BASELINE:
                        return ALIGN_BASELINE;
                }
                break;
            case BOTTOM:
                switch (to) {
                    case TOP:
                        return LAYOUT_ABOVE;
                    case BOTTOM:
                        return ALIGN_BOTTOM;
                    case BASELINE:
                        return ALIGN_BASELINE;
                }
                break;
            case LEFT:
                switch (to) {
                    case LEFT:
                        return ALIGN_LEFT;
                    case RIGHT:
                        return LAYOUT_RIGHT_OF;
                }
                break;
            case RIGHT:
                switch (to) {
                    case LEFT:
                        return LAYOUT_LEFT_OF;
                    case RIGHT:
                        return ALIGN_RIGHT;
                }
                break;
            case BASELINE:
                return ALIGN_BASELINE;
        }

        return null;
    }
}