/*
 * Copyright (C) 2012 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 android.hardware.input;

import android.annotation.NonNull;
import android.os.LocaleList;
import android.os.Parcel;
import android.os.Parcelable;

import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;

/**
 * Describes a keyboard layout.
 *
 * @hide
 */
public final class KeyboardLayout implements Parcelable, Comparable<KeyboardLayout> {

    /** Undefined keyboard layout */
    public static final String LAYOUT_TYPE_UNDEFINED = "undefined";

    /** Qwerty-based keyboard layout */
    public static final String LAYOUT_TYPE_QWERTY = "qwerty";

    /** Qwertz-based keyboard layout */
    public static final String LAYOUT_TYPE_QWERTZ = "qwertz";

    /** Azerty-based keyboard layout */
    public static final String LAYOUT_TYPE_AZERTY = "azerty";

    /** Dvorak keyboard layout */
    public static final String LAYOUT_TYPE_DVORAK = "dvorak";

    /** Colemak keyboard layout */
    public static final String LAYOUT_TYPE_COLEMAK = "colemak";

    /** Workman keyboard layout */
    public static final String LAYOUT_TYPE_WORKMAN = "workman";

    /** Turkish-F keyboard layout */
    public static final String LAYOUT_TYPE_TURKISH_F = "turkish_f";

    /** Turkish-Q keyboard layout */
    public static final String LAYOUT_TYPE_TURKISH_Q = "turkish_q";

    /** Keyboard layout that has been enhanced with a large number of extra characters */
    public static final String LAYOUT_TYPE_EXTENDED = "extended";

    private final String mDescriptor;
    private final String mLabel;
    private final String mCollection;
    private final int mPriority;
    @NonNull
    private final LocaleList mLocales;
    private final LayoutType mLayoutType;
    private final int mVendorId;
    private final int mProductId;

    /** Currently supported Layout types in the KCM files */
    public enum LayoutType {
        UNDEFINED(0, LAYOUT_TYPE_UNDEFINED),
        QWERTY(1, LAYOUT_TYPE_QWERTY),
        QWERTZ(2, LAYOUT_TYPE_QWERTZ),
        AZERTY(3, LAYOUT_TYPE_AZERTY),
        DVORAK(4, LAYOUT_TYPE_DVORAK),
        COLEMAK(5, LAYOUT_TYPE_COLEMAK),
        WORKMAN(6, LAYOUT_TYPE_WORKMAN),
        TURKISH_Q(7, LAYOUT_TYPE_TURKISH_Q),
        TURKISH_F(8, LAYOUT_TYPE_TURKISH_F),
        EXTENDED(9, LAYOUT_TYPE_EXTENDED);

        private final int mValue;
        private final String mName;
        private static final Map<Integer, LayoutType> VALUE_TO_ENUM_MAP = new HashMap<>();
        private static final Map<String, LayoutType> NAME_TO_ENUM_MAP = new HashMap<>();
        static {
            for (LayoutType type : LayoutType.values()) {
                VALUE_TO_ENUM_MAP.put(type.mValue, type);
                NAME_TO_ENUM_MAP.put(type.mName, type);
            }
        }

        private static LayoutType of(int value) {
            return VALUE_TO_ENUM_MAP.getOrDefault(value, UNDEFINED);
        }

        LayoutType(int value, String name) {
            this.mValue = value;
            this.mName = name;
        }

        private int getValue() {
            return mValue;
        }

        private String getName() {
            return mName;
        }

        /**
         * Returns enum value for provided layout type
         * @param layoutName name of the layout type
         * @return int value corresponding to the LayoutType enum that matches the layout name.
         * (LayoutType.UNDEFINED if no match found)
         */
        public static int getLayoutTypeEnumValue(String layoutName) {
            return NAME_TO_ENUM_MAP.getOrDefault(layoutName, UNDEFINED).getValue();
        }

        /**
         * Returns name for provided layout type enum value
         * @param enumValue value representation for LayoutType enum
         * @return Layout name corresponding to the enum value (LAYOUT_TYPE_UNDEFINED if not found)
         */
        public static String getLayoutNameFromValue(int enumValue) {
            return VALUE_TO_ENUM_MAP.getOrDefault(enumValue, UNDEFINED).getName();
        }
    }

    @NonNull
    public static final Parcelable.Creator<KeyboardLayout> CREATOR = new Parcelable.Creator<>() {
        public KeyboardLayout createFromParcel(Parcel source) {
            return new KeyboardLayout(source);
        }
        public KeyboardLayout[] newArray(int size) {
            return new KeyboardLayout[size];
        }
    };

    public KeyboardLayout(String descriptor, String label, String collection, int priority,
            LocaleList locales, int layoutValue, int vid, int pid) {
        mDescriptor = descriptor;
        mLabel = label;
        mCollection = collection;
        mPriority = priority;
        mLocales = locales;
        mLayoutType = LayoutType.of(layoutValue);
        mVendorId = vid;
        mProductId = pid;
    }

    private KeyboardLayout(Parcel source) {
        mDescriptor = source.readString();
        mLabel = source.readString();
        mCollection = source.readString();
        mPriority = source.readInt();
        mLocales = LocaleList.CREATOR.createFromParcel(source);
        mLayoutType = LayoutType.of(source.readInt());
        mVendorId = source.readInt();
        mProductId = source.readInt();
    }

    /**
     * Gets the keyboard layout descriptor, which can be used to retrieve
     * the keyboard layout again later using
     * {@link InputManager#getKeyboardLayout(String)}.
     *
     * @return The keyboard layout descriptor.
     */
    public String getDescriptor() {
        return mDescriptor;
    }

    /**
     * Gets the keyboard layout descriptive label to show in the user interface.
     * @return The keyboard layout descriptive label.
     */
    public String getLabel() {
        return mLabel;
    }

    /**
     * Gets the name of the collection to which the keyboard layout belongs.  This is
     * the label of the broadcast receiver or application that provided the keyboard layout.
     * @return The keyboard layout collection name.
     */
    public String getCollection() {
        return mCollection;
    }

    /**
     * Gets the locales that this keyboard layout is intended for.
     * This may be empty if a locale has not been assigned to this keyboard layout.
     * @return The keyboard layout's intended locale.
     */
    public LocaleList getLocales() {
        return mLocales;
    }

    /**
     * Gets the layout type that this keyboard layout is intended for.
     * This may be "undefined" if a layoutType has not been assigned to this keyboard layout.
     * @return The keyboard layout's intended layout type.
     */
    public String getLayoutType() {
        return mLayoutType.getName();
    }

    /**
     * Gets the vendor ID of the hardware device this keyboard layout is intended for.
     * Returns -1 if this is not specific to any piece of hardware.
     * @return The hardware vendor ID of the keyboard layout's intended device.
     */
    public int getVendorId() {
        return mVendorId;
    }

    /**
     * Gets the product ID of the hardware device this keyboard layout is intended for.
     * Returns -1 if this is not specific to any piece of hardware.
     * @return The hardware product ID of the keyboard layout's intended device.
     */
    public int getProductId() {
        return mProductId;
    }

    /**
     * Returns if the Keyboard layout follows the ANSI Physical key layout.
     */
    public boolean isAnsiLayout() {
        for (int i = 0; i < mLocales.size(); i++) {
            Locale locale = mLocales.get(i);
            if (locale != null && locale.getCountry().equalsIgnoreCase("us")
                    && mLayoutType != LayoutType.EXTENDED) {
                return true;
            }
        }
        return false;
    }

    /**
     * Returns if the Keyboard layout follows the JIS Physical key layout.
     */
    public boolean isJisLayout() {
        for (int i = 0; i < mLocales.size(); i++) {
            Locale locale = mLocales.get(i);
            if (locale != null && locale.getCountry().equalsIgnoreCase("jp")) {
                return true;
            }
        }
        return false;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(mDescriptor);
        dest.writeString(mLabel);
        dest.writeString(mCollection);
        dest.writeInt(mPriority);
        mLocales.writeToParcel(dest, 0);
        dest.writeInt(mLayoutType.getValue());
        dest.writeInt(mVendorId);
        dest.writeInt(mProductId);
    }

    @Override
    public int compareTo(KeyboardLayout another) {
        // Note that these arguments are intentionally flipped since you want higher priority
        // keyboards to be listed before lower priority keyboards.
        int result = Integer.compare(another.mPriority, mPriority);
        if (result == 0) {
            result = Integer.compare(mLayoutType.mValue, another.mLayoutType.mValue);
        }
        if (result == 0) {
            result = mLabel.compareToIgnoreCase(another.mLabel);
        }
        if (result == 0) {
            result = mCollection.compareToIgnoreCase(another.mCollection);
        }
        return result;
    }

    @Override
    public String toString() {
        String collectionString = mCollection.isEmpty() ? "" : " - " + mCollection;
        return "KeyboardLayout " + mLabel + collectionString
                + ", descriptor: " + mDescriptor
                + ", priority: " + mPriority
                + ", locales: " + mLocales.toString()
                + ", layout type: " + mLayoutType.getName()
                + ", vendorId: " + mVendorId
                + ", productId: " + mProductId;
    }

    /**
     * Check if the provided layout type is supported/valid.
     *
     * @param layoutName name of layout type
     * @return {@code true} if the provided layout type is supported/valid.
     */
    public static boolean isLayoutTypeValid(@NonNull String layoutName) {
        Objects.requireNonNull(layoutName, "Provided layout name should not be null");
        for (LayoutType layoutType : LayoutType.values()) {
            if (layoutName.equals(layoutType.getName())) {
                return true;
            }
        }
        // Layout doesn't match any supported layout types
        return false;
    }
}
