// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.components.browser_ui.widget;

import static org.junit.Assert.fail;
import static org.mockito.Mockito.verify;

import android.content.Context;
import android.text.TextUtils;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup.MarginLayoutParams;
import android.widget.EditText;
import android.widget.RadioGroup;
import android.widget.TextView;

import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;

import org.chromium.base.test.BaseJUnit4ClassRunner;
import org.chromium.base.test.UiThreadTest;
import org.chromium.base.test.util.Batch;
import org.chromium.components.browser_ui.widget.test.R;

import java.util.Arrays;
import java.util.List;

/**
 * Tests for {@link RadioButtonLayout}.
 */
@RunWith(BaseJUnit4ClassRunner.class)
@Batch(Batch.UNIT_TESTS)
public class RadioButtonWithDescriptionLayoutTest {
    private static final String ZERO_MARGIN_ASSERT_MESSAGE =
            "The last item should have a zero margin";
    private static final String PRIMARY_MATCH_ASSERT_MESSAGE =
            "Primary text set through addButtons should match the view's primary text.";
    private static final String DESCRIPTION_MATCH_ASSERT_MESSAGE =
            "Description set through addButtons should match the view's description.";
    private static final String TAG_MATCH_ASSERT_MESSAGE =
            "Tag set through addButtons should match the view's tag.";

    private static final String PRIMARY_MATCH_FROM_XML_ASSERT_MESSAGE =
            "Primary text set through layout should match the view's primary text.";
    private static final String DESC_MATCH_FROM_XML_ASSERT_MESSAGE =
            "Description text set through layout should match the view's description.";
    private static final String HINT_MATCH_FROM_XML_ASSERT_MESSAGE =
            "Hint message set through layout should match the view's hint message.";

    private Context mContext;

    @Before
    public void setUp() {
        mContext = new ContextThemeWrapper(
                InstrumentationRegistry.getTargetContext(), R.style.Theme_BrowserUI_DayNight);
    }

    @Test
    @SmallTest
    @UiThreadTest
    public void testMargins() {
        RadioButtonWithDescriptionLayout layout = new RadioButtonWithDescriptionLayout(mContext);

        // Add one set of buttons.
        List<RadioButtonWithDescription> buttons =
                Arrays.asList(createRadioButtonWithDescription("a", "a_desc", "a_tag"),
                        createRadioButtonWithDescription("b", "b_desc", "b_tag"),
                        createRadioButtonWithDescription("c", "c_desc", "c_tag"));
        layout.addButtons(buttons);
        Assert.assertEquals(3, layout.getChildCount());

        // Test the margins.
        for (int i = 0; i < layout.getChildCount(); i++) {
            View child = layout.getChildAt(i);
            MarginLayoutParams params = (MarginLayoutParams) child.getLayoutParams();
            Assert.assertEquals(ZERO_MARGIN_ASSERT_MESSAGE, 0, params.bottomMargin);
        }

        // Add more buttons.
        List<RadioButtonWithDescription> moreButtons =
                Arrays.asList(createRadioButtonWithDescription("d", "d_desc", null),
                        createRadioButtonWithDescription("e", "e_desc", null),
                        createRadioButtonWithDescription("f", "f_desc", null));
        layout.addButtons(moreButtons);
        Assert.assertEquals(6, layout.getChildCount());

        // Test the margins.
        for (int i = 0; i < layout.getChildCount(); i++) {
            View child = layout.getChildAt(i);
            MarginLayoutParams params = (MarginLayoutParams) child.getLayoutParams();
            Assert.assertEquals(ZERO_MARGIN_ASSERT_MESSAGE, 0, params.bottomMargin);
        }
    }

    @Test
    @SmallTest
    @UiThreadTest
    public void testAddButtons() {
        RadioButtonWithDescriptionLayout layout = new RadioButtonWithDescriptionLayout(mContext);

        // Add one set of options.
        List<RadioButtonWithDescription> buttons =
                Arrays.asList(createRadioButtonWithDescription("a", "a_desc", "a_tag"),
                        createRadioButtonWithDescription("b", "b_desc", "b_tag"),
                        createRadioButtonWithDescription("c", "c_desc", "c_tag"));
        layout.addButtons(buttons);
        Assert.assertEquals(3, layout.getChildCount());

        for (int i = 0; i < layout.getChildCount(); i++) {
            RadioButtonWithDescription b = (RadioButtonWithDescription) layout.getChildAt(i);
            Assert.assertEquals(PRIMARY_MATCH_ASSERT_MESSAGE,
                    buttons.get(i).getPrimaryText().toString(), b.getPrimaryText().toString());
            Assert.assertEquals(DESCRIPTION_MATCH_ASSERT_MESSAGE,
                    buttons.get(i).getDescriptionText().toString(),
                    b.getDescriptionText().toString());
            Assert.assertEquals(TAG_MATCH_ASSERT_MESSAGE, buttons.get(i).getTag(), b.getTag());
        }

        // Add even more options, but without tags.
        List<RadioButtonWithDescription> moreButtons =
                Arrays.asList(createRadioButtonWithDescription("d", "d_desc", null),
                        createRadioButtonWithDescription("e", "e_desc", null),
                        createRadioButtonWithDescription("f", "f_desc", null));
        layout.addButtons(moreButtons);
        Assert.assertEquals(6, layout.getChildCount());
        for (int i = 0; i < 3; i++) {
            RadioButtonWithDescription b = (RadioButtonWithDescription) layout.getChildAt(i);
            Assert.assertEquals(PRIMARY_MATCH_ASSERT_MESSAGE,
                    buttons.get(i).getPrimaryText().toString(), b.getPrimaryText().toString());
            Assert.assertEquals(DESCRIPTION_MATCH_ASSERT_MESSAGE,
                    buttons.get(i).getDescriptionText().toString(),
                    b.getDescriptionText().toString());
            Assert.assertEquals(TAG_MATCH_ASSERT_MESSAGE, buttons.get(i).getTag(), b.getTag());
        }
        for (int i = 3; i < 6; i++) {
            RadioButtonWithDescription b = (RadioButtonWithDescription) layout.getChildAt(i);
            Assert.assertEquals(PRIMARY_MATCH_ASSERT_MESSAGE,
                    moreButtons.get(i - 3).getPrimaryText().toString(),
                    b.getPrimaryText().toString());
            Assert.assertEquals(DESCRIPTION_MATCH_ASSERT_MESSAGE,
                    moreButtons.get(i - 3).getDescriptionText().toString(),
                    b.getDescriptionText().toString());
            Assert.assertEquals(
                    TAG_MATCH_ASSERT_MESSAGE, moreButtons.get(i - 3).getTag(), b.getTag());
        }
    }

    @Test
    @SmallTest
    @UiThreadTest
    public void testSelection() {
        final RadioButtonWithDescriptionLayout layout =
                new RadioButtonWithDescriptionLayout(mContext);

        // Add one set of options.
        List<RadioButtonWithDescription> buttons =
                Arrays.asList(createRadioButtonWithDescription("a", "a_desc", null),
                        createRadioButtonWithDescription("b", "b_desc", null),
                        createRadioButtonWithDescription("c", "c_desc", null));
        layout.addButtons(buttons);
        Assert.assertEquals(3, layout.getChildCount());

        // Nothing should be selected by default.
        for (int i = 0; i < layout.getChildCount(); i++) {
            RadioButtonWithDescription child = (RadioButtonWithDescription) layout.getChildAt(i);
            Assert.assertFalse(child.isChecked());
        }

        // Select the second one.
        layout.selectChildAtIndexForTesting(1);
        for (int i = 0; i < layout.getChildCount(); i++) {
            RadioButtonWithDescription child = (RadioButtonWithDescription) layout.getChildAt(i);
            Assert.assertEquals(i == 1, child.isChecked());
        }

        // Add even more options.
        List<RadioButtonWithDescription> moreButtons =
                Arrays.asList(createRadioButtonWithDescription("d", "d_desc", null),
                        createRadioButtonWithDescription("e", "e_desc", null),
                        createRadioButtonWithDescription("f", "f_desc", null));
        layout.addButtons(moreButtons);
        Assert.assertEquals(6, layout.getChildCount());

        // Second child should still be checked.
        for (int i = 0; i < layout.getChildCount(); i++) {
            RadioButtonWithDescription child = (RadioButtonWithDescription) layout.getChildAt(i);
            Assert.assertEquals(i == 1, child.isChecked());
        }
    }

    @Test
    @SmallTest
    @UiThreadTest
    public void testAccessoryViewAdded() {
        final RadioButtonWithDescriptionLayout layout =
                new RadioButtonWithDescriptionLayout(mContext);

        List<RadioButtonWithDescription> buttons =
                Arrays.asList(createRadioButtonWithDescription("a", "a_desc", null),
                        createRadioButtonWithDescription("b", "b_desc", null),
                        createRadioButtonWithDescription("c", "c_desc", null));
        layout.addButtons(buttons);

        RadioButtonWithDescription firstButton = (RadioButtonWithDescription) layout.getChildAt(0);
        final TextView accessoryTextView = new TextView(mContext);
        layout.attachAccessoryView(accessoryTextView, firstButton);
        Assert.assertEquals(
                "The accessory view should be right after the position of it's attachment host.",
                accessoryTextView, layout.getChildAt(1));
    }

    @Test
    @SmallTest
    @UiThreadTest
    public void testAccessoryViewAddedThenReadded() {
        final RadioButtonWithDescriptionLayout layout =
                new RadioButtonWithDescriptionLayout(mContext);

        List<RadioButtonWithDescription> buttons =
                Arrays.asList(createRadioButtonWithDescription("a", "a_desc", null),
                        createRadioButtonWithDescription("b", "b_desc", null),
                        createRadioButtonWithDescription("c", "c_desc", null));
        layout.addButtons(buttons);

        RadioButtonWithDescription firstButton = (RadioButtonWithDescription) layout.getChildAt(0);
        RadioButtonWithDescription lastButton =
                (RadioButtonWithDescription) layout.getChildAt(layout.getChildCount() - 1);
        final TextView accessoryTextView = new TextView(mContext);
        layout.attachAccessoryView(accessoryTextView, firstButton);
        layout.attachAccessoryView(accessoryTextView, lastButton);
        Assert.assertNotEquals(
                "The accessory view shouldn't be in the first position it was inserted at.",
                accessoryTextView, layout.getChildAt(1));
        Assert.assertEquals("The accessory view should be at the new position it was placed at.",
                accessoryTextView, layout.getChildAt(layout.getChildCount() - 1));
    }

    @Test
    @SmallTest
    @UiThreadTest
    public void testCombinedRadioButtons() {
        // Test if radio buttons are set up correctly when there are multiple classes in the same
        // layout.
        View content = LayoutInflater.from(mContext).inflate(
                R.layout.radio_button_with_description_layout_test, null, false);

        RadioButtonWithDescriptionLayout layout =
                content.findViewById(R.id.test_radio_button_layout);
        RadioButtonWithDescription b1 = content.findViewById(R.id.test_radio_description_1);
        RadioButtonWithDescription b2 = content.findViewById(R.id.test_radio_description_2);
        RadioButtonWithEditText b3 = content.findViewById(R.id.test_radio_edit_text_1);
        RadioButtonWithEditText b4 = content.findViewById(R.id.test_radio_edit_text_2);
        RadioButtonWithDescriptionAndAuxButton b5 =
                content.findViewById(R.id.test_radio_description_and_aux_button_1);

        Assert.assertNotNull(layout);
        Assert.assertNotNull(b1);
        Assert.assertNotNull(b2);
        Assert.assertNotNull(b3);
        Assert.assertNotNull(b4);
        Assert.assertNotNull(b5);

        layout.selectChildAtIndexForTesting(3);

        Assert.assertEquals(PRIMARY_MATCH_FROM_XML_ASSERT_MESSAGE, b1.getPrimaryText().toString(),
                mContext.getResources().getString(R.string.test_primary_1));
        Assert.assertEquals(PRIMARY_MATCH_FROM_XML_ASSERT_MESSAGE, b2.getPrimaryText().toString(),
                mContext.getResources().getString(R.string.test_primary_2));
        Assert.assertEquals(PRIMARY_MATCH_FROM_XML_ASSERT_MESSAGE, b3.getPrimaryText().toString(),
                mContext.getResources().getString(R.string.test_primary_3));
        Assert.assertEquals(PRIMARY_MATCH_FROM_XML_ASSERT_MESSAGE, b4.getPrimaryText().toString(),
                mContext.getResources().getString(R.string.test_primary_4));
        Assert.assertEquals(PRIMARY_MATCH_FROM_XML_ASSERT_MESSAGE, b5.getPrimaryText().toString(),
                mContext.getResources().getString(R.string.test_primary_5));

        Assert.assertTrue(
                DESC_MATCH_FROM_XML_ASSERT_MESSAGE, TextUtils.isEmpty(b1.getDescriptionText()));
        Assert.assertEquals(DESC_MATCH_FROM_XML_ASSERT_MESSAGE, b2.getDescriptionText().toString(),
                mContext.getResources().getString(R.string.test_desc_2));
        Assert.assertTrue(
                DESC_MATCH_FROM_XML_ASSERT_MESSAGE, TextUtils.isEmpty(b3.getDescriptionText()));
        Assert.assertEquals(DESC_MATCH_FROM_XML_ASSERT_MESSAGE, b4.getDescriptionText().toString(),
                mContext.getResources().getString(R.string.test_desc_4));
        Assert.assertEquals(DESC_MATCH_FROM_XML_ASSERT_MESSAGE, b5.getDescriptionText().toString(),
                mContext.getResources().getString(R.string.test_desc_5));

        Assert.assertEquals(HINT_MATCH_FROM_XML_ASSERT_MESSAGE,
                ((EditText) b3.getPrimaryTextView()).getHint().toString(),
                mContext.getResources().getString(R.string.test_uri));
        Assert.assertEquals(HINT_MATCH_FROM_XML_ASSERT_MESSAGE,
                ((EditText) b4.getPrimaryTextView()).getHint().toString(),
                mContext.getResources().getString(R.string.test_uri));

        Assert.assertFalse(b1.isChecked());
        Assert.assertFalse(b2.isChecked());
        Assert.assertFalse(b3.isChecked());
        Assert.assertTrue(b4.isChecked());
        Assert.assertFalse(b5.isChecked());
    }

    @Test
    @SmallTest
    public void testSetEnable() {
        View content = LayoutInflater.from(mContext).inflate(
                R.layout.radio_button_with_description_layout_test, null, false);

        RadioButtonWithDescriptionLayout layout =
                content.findViewById(R.id.test_radio_button_layout);
        RadioButtonWithDescription b1 = content.findViewById(R.id.test_radio_description_1);
        RadioButtonWithDescription b2 = content.findViewById(R.id.test_radio_description_2);
        RadioButtonWithEditText b3 = content.findViewById(R.id.test_radio_edit_text_1);
        RadioButtonWithEditText b4 = content.findViewById(R.id.test_radio_edit_text_2);

        final TextView textView1 = new TextView(mContext);
        final TextView textView3 = new TextView(mContext);

        layout.attachAccessoryView(textView1, b1);
        layout.attachAccessoryView(textView3, b3);

        layout.setEnabled(false);

        Assert.assertFalse(b1.isEnabled());
        Assert.assertFalse(b2.isEnabled());
        Assert.assertFalse(b3.isEnabled());
        Assert.assertFalse(b4.isEnabled());
        Assert.assertFalse(textView1.isEnabled());
        Assert.assertFalse(textView3.isEnabled());

        layout.setEnabled(true);

        Assert.assertTrue(b1.isEnabled());
        Assert.assertTrue(b2.isEnabled());
        Assert.assertTrue(b3.isEnabled());
        Assert.assertTrue(b4.isEnabled());
        Assert.assertTrue(textView1.isEnabled());
        Assert.assertTrue(textView3.isEnabled());
    }

    @Test
    @SmallTest
    public void testOnButtonCheckedStateChanged() {
        View content = LayoutInflater.from(mContext).inflate(
                R.layout.radio_button_with_description_layout_test, null, false);
        RadioButtonWithDescriptionLayout layout =
                content.findViewById(R.id.test_radio_button_layout);
        RadioButtonWithDescription b1 = content.findViewById(R.id.test_radio_description_1);

        RadioGroup.OnCheckedChangeListener listener =
                Mockito.mock(RadioGroup.OnCheckedChangeListener.class);
        layout.setOnCheckedChangeListener(listener);

        layout.onButtonCheckedStateChanged(b1);
        verify(listener).onCheckedChanged(layout, b1.getId());
    }

    @Test
    @SmallTest
    public void testOnButtonCheckedStateChanged_nullObserver() {
        View content = LayoutInflater.from(mContext).inflate(
                R.layout.radio_button_with_description_layout_test, null, false);
        RadioButtonWithDescriptionLayout layout =
                content.findViewById(R.id.test_radio_button_layout);
        RadioButtonWithDescription b1 = content.findViewById(R.id.test_radio_description_1);

        layout.setOnCheckedChangeListener(null);

        try {
            layout.onButtonCheckedStateChanged(b1);
        } catch (NullPointerException e) {
            fail("No exception should be thrown when the observer is null");
        }
    }

    private RadioButtonWithDescription createRadioButtonWithDescription(
            String primary, String description, Object tag) {
        RadioButtonWithDescription b = new RadioButtonWithDescription(mContext, null);
        b.setPrimaryText(primary);
        b.setDescriptionText(description);
        b.setTag(tag);
        return b;
    }
}
