/*
 * Copyright (C) 2015 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.tv.data;

import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.view.KeyEvent;
import com.android.tv.common.util.StringUtils;
import com.android.tv.data.api.Channel;
import java.util.Objects;

/** A convenience class to handle channel number. */
public final class ChannelNumber implements Comparable<ChannelNumber> {
    private static final int[] CHANNEL_DELIMITER_KEYCODES = {
        KeyEvent.KEYCODE_MINUS,
        KeyEvent.KEYCODE_NUMPAD_SUBTRACT,
        KeyEvent.KEYCODE_PERIOD,
        KeyEvent.KEYCODE_NUMPAD_DOT,
        KeyEvent.KEYCODE_SPACE
    };

    /** The major part of the channel number. */
    public String majorNumber;
    /** The flag which indicates whether it has a delimiter or not. */
    public boolean hasDelimiter;
    /** The major part of the channel number. */
    public String minorNumber;

    public ChannelNumber() {
        reset();
    }

    /**
     * {@code lhs} and {@code rhs} are equivalent if {@link ChannelNumber#compare(String, String)}
     * is 0 or if only one has a delimiter and both {@link ChannelNumber#majorNumber} equals.
     */
    public static boolean equivalent(String lhs, String rhs) {
        if (compare(lhs, rhs) == 0) {
            return true;
        }
        // Match if only one has delimiter
        ChannelNumber lhsNumber = parseChannelNumber(lhs);
        ChannelNumber rhsNumber = parseChannelNumber(rhs);
        return lhsNumber != null
                && rhsNumber != null
                && lhsNumber.hasDelimiter != rhsNumber.hasDelimiter
                && lhsNumber.majorNumber.equals(rhsNumber.majorNumber);
    }

    public void reset() {
        setChannelNumber("", false, "");
    }

    private void setChannelNumber(String majorNumber, boolean hasDelimiter, String minorNumber) {
        this.majorNumber = majorNumber;
        this.hasDelimiter = hasDelimiter;
        this.minorNumber = minorNumber;
    }

    @Override
    public String toString() {
        if (hasDelimiter) {
            return majorNumber + Channel.CHANNEL_NUMBER_DELIMITER + minorNumber;
        }
        return majorNumber;
    }

    @Override
    public int compareTo(@NonNull ChannelNumber another) {
        int major = Integer.parseInt(majorNumber);
        int minor = hasDelimiter ? Integer.parseInt(minorNumber) : 0;

        int opponentMajor = Integer.parseInt(another.majorNumber);
        int opponentMinor = another.hasDelimiter ? Integer.parseInt(another.minorNumber) : 0;
        if (major == opponentMajor) {
            return minor - opponentMinor;
        }
        return major - opponentMajor;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof ChannelNumber) {
            ChannelNumber channelNumber = (ChannelNumber) obj;
            return TextUtils.equals(majorNumber, channelNumber.majorNumber)
                    && TextUtils.equals(minorNumber, channelNumber.minorNumber)
                    && hasDelimiter == channelNumber.hasDelimiter;
        }
        return super.equals(obj);
    }

    @Override
    public int hashCode() {
        return Objects.hash(majorNumber, hasDelimiter, minorNumber);
    }

    public static boolean isChannelNumberDelimiterKey(int keyCode) {
        for (int delimiterKeyCode : CHANNEL_DELIMITER_KEYCODES) {
            if (delimiterKeyCode == keyCode) {
                return true;
            }
        }
        return false;
    }

    /**
     * Returns the ChannelNumber instance.
     *
     * <p>Note that all the channel number argument should be normalized by {@link
     * ChannelImpl#normalizeDisplayNumber}. The channels retrieved from {@link ChannelDataManager}
     * are already normalized.
     */
    public static ChannelNumber parseChannelNumber(String number) {
        if (number == null) {
            return null;
        }
        ChannelNumber ret = new ChannelNumber();
        int indexOfDelimiter = number.indexOf(Channel.CHANNEL_NUMBER_DELIMITER);
        if (indexOfDelimiter == 0 || indexOfDelimiter == number.length() - 1) {
            return null;
        } else if (indexOfDelimiter < 0) {
            ret.majorNumber = number;
            if (!isInteger(ret.majorNumber)) {
                return null;
            }
        } else {
            ret.hasDelimiter = true;
            ret.majorNumber = number.substring(0, indexOfDelimiter);
            ret.minorNumber = number.substring(indexOfDelimiter + 1);
            if (!isInteger(ret.majorNumber) || !isInteger(ret.minorNumber)) {
                return null;
            }
        }
        return ret;
    }

    /**
     * Compares the channel numbers.
     *
     * <p>Note that all the channel number arguments should be normalized by {@link
     * ChannelImpl#normalizeDisplayNumber}. The channels retrieved from {@link ChannelDataManager}
     * are already normalized.
     */
    public static int compare(String lhs, String rhs) {
        ChannelNumber lhsNumber = parseChannelNumber(lhs);
        ChannelNumber rhsNumber = parseChannelNumber(rhs);
        // Null first
        if (lhsNumber == null && rhsNumber == null) {
            return StringUtils.compare(lhs, rhs);
        } else if (lhsNumber == null /* && rhsNumber != null */) {
            return -1;
        } else if (rhsNumber == null) {
            return 1;
        }
        return lhsNumber.compareTo(rhsNumber);
    }

    private static boolean isInteger(String string) {
        try {
            Integer.parseInt(string);
        } catch (NumberFormatException | NullPointerException e) {
            return false;
        }
        return true;
    }
}
