/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.pdfbox.pdmodel.interactive.form;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;

import java.io.File;
import java.io.IOException;

import org.apache.pdfbox.Loader;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.PDDocument;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;


/**
 * Test for the PDButton class.
 *
 */
class PDButtonTest
{
    private static final File IN_DIR = new File("src/test/resources/org/apache/pdfbox/pdmodel/interactive/form");
    private static final String NAME_OF_PDF = "AcroFormsBasicFields.pdf";
    private static final File TARGET_PDF_DIR = new File("target/pdfs");

    private PDDocument document;
    private PDAcroForm acroForm;

    private PDDocument acrobatDocument;
    private PDAcroForm acrobatAcroForm;
    
    
    @BeforeEach
    public void setUp() throws IOException
    {
        document = new PDDocument();
        acroForm = new PDAcroForm(document);
        
        acrobatDocument = Loader.loadPDF(new File(IN_DIR, NAME_OF_PDF));
        acrobatAcroForm = acrobatDocument.getDocumentCatalog().getAcroForm();
    }

    @Test
    void createCheckBox()
    {
        PDButton buttonField = new PDCheckBox(acroForm);
        
        assertEquals(buttonField.getFieldType(), buttonField.getCOSObject().getNameAsString(COSName.FT));
        assertEquals("Btn", buttonField.getFieldType());
        assertFalse(buttonField.isPushButton());
        assertFalse(buttonField.isRadioButton());
    }

    @Test
    void createPushButton()
    {
        PDButton buttonField = new PDPushButton(acroForm);
        
        assertEquals(buttonField.getFieldType(), buttonField.getCOSObject().getNameAsString(COSName.FT));
        assertEquals("Btn", buttonField.getFieldType());
        assertTrue(buttonField.isPushButton());
        assertFalse(buttonField.isRadioButton());
    }

    @Test
    void createRadioButton()
    {
        PDButton buttonField = new PDRadioButton(acroForm);
        
        assertEquals(buttonField.getFieldType(), buttonField.getCOSObject().getNameAsString(COSName.FT));
        assertEquals("Btn", buttonField.getFieldType());
        assertTrue(buttonField.isRadioButton());
        assertFalse(buttonField.isPushButton());
    }
    
    /**
     * PDFBOX-3656
     * 
     * Test a radio button with options.
     * This was causing an ArrayIndexOutOfBoundsException when trying to set to "Off", as this
     * wasn't treated to be a valid option.
     */
    @Test
    void testRadioButtonWithOptions()
    {
        File file = new File(TARGET_PDF_DIR, "PDFBOX-3656.pdf");
        
        try (PDDocument pdfDocument = Loader.loadPDF(file))
        {   
            PDRadioButton radioButton = (PDRadioButton) pdfDocument.getDocumentCatalog().getAcroForm().getField("Checking/Savings");
            radioButton.setValue("Off");
            radioButton.getWidgets().forEach(widget ->
                assertEquals(COSName.Off, widget.getCOSObject().getItem(COSName.AS),
                        "The widget should be set to Off"));
        }
        catch (IOException e)
        {
            fail("Unexpected IOException " + e.getMessage());
        }
    }
    
    /**
     * PDFBOX-3682
     * 
     * Test a radio button with options.
     * Special handling for a radio button with /Opt and the On state not being named
     * after the index.
     * 
     */
    @Test
    void testOptionsAndNamesNotNumbers()
    {
        File file = new File(TARGET_PDF_DIR, "PDFBOX-3682.pdf");
        try (PDDocument pdfDocument = Loader.loadPDF(file))
        {            
            pdfDocument.getDocumentCatalog().getAcroForm().getField("RadioButton").setValue("c");
            PDRadioButton radioButton = (PDRadioButton) pdfDocument.getDocumentCatalog().getAcroForm().getField("RadioButton");
            radioButton.setValue("c");

            // test that the old behavior is now invalid
            assertNotEquals("2", radioButton.getValueAsString(), "This shall no longer be 2");
            assertNotEquals("2",
                    radioButton.getWidgets().get(2).getCOSObject().getNameAsString(COSName.AS),
                    "This shall no longer be 2");
            
            // test for the correct behavior
            assertEquals("c", radioButton.getValueAsString(), "This shall be c");
            assertEquals("c",
                    radioButton.getWidgets().get(2).getCOSObject().getNameAsString(COSName.AS),
                    "This shall be c");
        }
        catch (IOException e)
        {
            fail("Unexpected IOException " + e.getMessage());
        }
    }
        
    @Test
    void retrieveAcrobatCheckBoxProperties()
    {
        PDCheckBox checkbox = (PDCheckBox) acrobatAcroForm.getField("Checkbox");
        assertNotNull(checkbox);
        assertEquals("Yes", checkbox.getOnValue());
        assertEquals(1, checkbox.getOnValues().size());
        assertTrue(checkbox.getOnValues().contains("Yes"));
    }
    
    @Test
    void testAcrobatCheckBoxProperties() throws IOException
    {
        PDCheckBox checkbox = (PDCheckBox) acrobatAcroForm.getField("Checkbox");
        assertEquals("Off", checkbox.getValue());
        assertEquals(false, checkbox.isChecked());

        checkbox.check();
        assertEquals(checkbox.getValue(), checkbox.getOnValue());
        assertEquals(true, checkbox.isChecked());

        checkbox.setValue("Yes");
        assertEquals(checkbox.getValue(), checkbox.getOnValue());
        assertEquals(true, checkbox.isChecked());
        assertEquals(COSName.YES, checkbox.getCOSObject().getDictionaryObject(COSName.AS));

        checkbox.setValue("Off");
        assertEquals(COSName.Off.getName(), checkbox.getValue());
        assertEquals(false, checkbox.isChecked());
        assertEquals(COSName.Off, checkbox.getCOSObject().getDictionaryObject(COSName.AS));

        checkbox = (PDCheckBox) acrobatAcroForm.getField("Checkbox-DefaultValue");
        assertEquals(checkbox.getDefaultValue(), checkbox.getOnValue());
        
        checkbox.setDefaultValue("Off");
        assertEquals(COSName.Off.getName(), checkbox.getDefaultValue());
    }
    
    @Test
    void setValueForAbstractedAcrobatCheckBox() throws IOException
    {
        PDField checkbox = acrobatAcroForm.getField("Checkbox");

        checkbox.setValue("Yes");
        assertEquals(checkbox.getValueAsString(), ((PDCheckBox) checkbox).getOnValue());
        assertEquals(true, ((PDCheckBox) checkbox).isChecked());
        assertEquals(COSName.YES, checkbox.getCOSObject().getDictionaryObject(COSName.AS));

        checkbox.setValue("Off");
        assertEquals(COSName.Off.getName(), checkbox.getValueAsString());
        assertEquals(false, ((PDCheckBox) checkbox).isChecked());
        assertEquals(COSName.Off, checkbox.getCOSObject().getDictionaryObject(COSName.AS));
    }
    
    @Test
    void testAcrobatCheckBoxGroupProperties() throws IOException
    {
        PDCheckBox checkbox = (PDCheckBox) acrobatAcroForm.getField("CheckboxGroup");
        assertEquals("Off", checkbox.getValue());
        assertEquals(false, checkbox.isChecked());

        checkbox.check();
        assertEquals(checkbox.getValue(), checkbox.getOnValue());
        assertEquals(true, checkbox.isChecked());

        assertEquals(3, checkbox.getOnValues().size());
        assertTrue(checkbox.getOnValues().contains("Option1"));
        assertTrue(checkbox.getOnValues().contains("Option2"));
        assertTrue(checkbox.getOnValues().contains("Option3"));

        // test a value which sets one of the individual checkboxes within the group
        checkbox.setValue("Option1");
        assertEquals("Option1", checkbox.getValue());
        assertEquals("Option1", checkbox.getValueAsString());

        // ensure that for the widgets representing the individual checkboxes
        // the AS entry has been set
        assertEquals("Option1", checkbox.getWidgets().get(0).getAppearanceState().getName());
        assertEquals("Off", checkbox.getWidgets().get(1).getAppearanceState().getName());
        assertEquals("Off", checkbox.getWidgets().get(2).getAppearanceState().getName());
        assertEquals("Off", checkbox.getWidgets().get(3).getAppearanceState().getName());

        // test a value which sets two of the individual chekboxes within the group
        // as the have the same name entry for being checked
        checkbox.setValue("Option3");
        assertEquals("Option3", checkbox.getValue());
        assertEquals("Option3", checkbox.getValueAsString());

        // ensure that for both widgets representing the individual checkboxes
        // the AS entry has been set
        assertEquals("Off", checkbox.getWidgets().get(0).getAppearanceState().getName());
        assertEquals("Off", checkbox.getWidgets().get(1).getAppearanceState().getName());
        assertEquals("Option3", checkbox.getWidgets().get(2).getAppearanceState().getName());
        assertEquals("Option3", checkbox.getWidgets().get(3).getAppearanceState().getName());
    }
    
    @Test
    void setValueForAbstractedCheckBoxGroup() throws IOException
    {
        PDField checkbox = acrobatAcroForm.getField("CheckboxGroup");

        // test a value which sets one of the individual checkboxes within the group
        checkbox.setValue("Option1");
        assertEquals("Option1",checkbox.getValueAsString());

        // ensure that for the widgets representing the individual checkboxes
        // the AS entry has been set
        assertEquals("Option1",checkbox.getWidgets().get(0).getAppearanceState().getName());
        assertEquals("Off",checkbox.getWidgets().get(1).getAppearanceState().getName());
        assertEquals("Off",checkbox.getWidgets().get(2).getAppearanceState().getName());
        assertEquals("Off",checkbox.getWidgets().get(3).getAppearanceState().getName());
        
        // test a value which sets two of the individual chekboxes within the group
        // as the have the same name entry for being checked
        checkbox.setValue("Option3");
        assertEquals("Option3",checkbox.getValueAsString());
        
        // ensure that for both widgets representing the individual checkboxes
        // the AS entry has been set
        assertEquals("Off",checkbox.getWidgets().get(0).getAppearanceState().getName());
        assertEquals("Off",checkbox.getWidgets().get(1).getAppearanceState().getName());
        assertEquals("Option3",checkbox.getWidgets().get(2).getAppearanceState().getName());
        assertEquals("Option3",checkbox.getWidgets().get(3).getAppearanceState().getName());
    }
    
    @Test
    void setCheckboxInvalidValue() throws IOException
    {
        PDCheckBox checkbox = (PDCheckBox) acrobatAcroForm.getField("Checkbox");
        // Set a value which doesn't match the radio button list 
        assertThrows(IllegalArgumentException.class, () -> checkbox.setValue("InvalidValue"));
    }    

    @Test
    void setCheckboxGroupInvalidValue() throws IOException
    {
        PDCheckBox checkbox = (PDCheckBox) acrobatAcroForm.getField("CheckboxGroup");
        // Set a value which doesn't match the radio button list 
        assertThrows(IllegalArgumentException.class, () -> checkbox.setValue("InvalidValue"));
    }    

    @Test
    void setAbstractedCheckboxInvalidValue() throws IOException
    {
        PDField checkbox = acrobatAcroForm.getField("Checkbox");
        // Set a value which doesn't match the radio button list 
        assertThrows(IllegalArgumentException.class, () -> checkbox.setValue("InvalidValue"));
    }    

    @Test
    void setAbstractedCheckboxGroupInvalidValue() throws IOException
    {
        PDField checkbox = acrobatAcroForm.getField("CheckboxGroup");
        // Set a value which doesn't match the radio button list
        assertThrows(IllegalArgumentException.class, () -> checkbox.setValue("InvalidValue"));
    }    

    @Test
    void retrieveAcrobatRadioButtonProperties()
    {
        PDRadioButton radioButton = (PDRadioButton) acrobatAcroForm.getField("RadioButtonGroup");
        assertNotNull(radioButton);
        assertEquals(2, radioButton.getOnValues().size());
        assertTrue(radioButton.getOnValues().contains("RadioButton01"));
        assertTrue(radioButton.getOnValues().contains("RadioButton02"));
    }
    
    @Test
    void testAcrobatRadioButtonProperties() throws IOException
    {
        PDRadioButton radioButton = (PDRadioButton) acrobatAcroForm.getField("RadioButtonGroup");

        // Set value so that first radio button option is selected
        radioButton.setValue("RadioButton01");
        assertEquals("RadioButton01", radioButton.getValue());
        // First option shall have /RadioButton01, second shall have /Off
        assertEquals(COSName.getPDFName("RadioButton01"),
                radioButton.getWidgets().get(0).getCOSObject().getDictionaryObject(COSName.AS));
        assertEquals(COSName.Off,
                radioButton.getWidgets().get(1).getCOSObject().getDictionaryObject(COSName.AS));

        // Set value so that second radio button option is selected
        radioButton.setValue("RadioButton02");
        assertEquals("RadioButton02", radioButton.getValue());
        // First option shall have /Off, second shall have /RadioButton02
        assertEquals(COSName.Off,
                radioButton.getWidgets().get(0).getCOSObject().getDictionaryObject(COSName.AS));
        assertEquals(COSName.getPDFName("RadioButton02"),
                radioButton.getWidgets().get(1).getCOSObject().getDictionaryObject(COSName.AS));
    }
    
    @Test
    void setValueForAbstractedAcrobatRadioButton() throws IOException
    {
        PDField radioButton = acrobatAcroForm.getField("RadioButtonGroup");

        // Set value so that first radio button option is selected
        radioButton.setValue("RadioButton01");
        assertEquals("RadioButton01", radioButton.getValueAsString());
        // First option shall have /RadioButton01, second shall have /Off
        assertEquals(COSName.getPDFName("RadioButton01"),
                radioButton.getWidgets().get(0).getCOSObject().getDictionaryObject(COSName.AS));
        assertEquals(COSName.Off,
                radioButton.getWidgets().get(1).getCOSObject().getDictionaryObject(COSName.AS));

        // Set value so that second radio button option is selected
        radioButton.setValue("RadioButton02");
        assertEquals("RadioButton02", radioButton.getValueAsString());
        // First option shall have /Off, second shall have /RadioButton02
        assertEquals(COSName.Off,
                radioButton.getWidgets().get(0).getCOSObject().getDictionaryObject(COSName.AS));
        assertEquals(COSName.getPDFName("RadioButton02"),
                radioButton.getWidgets().get(1).getCOSObject().getDictionaryObject(COSName.AS));
    }
    
    @Test
    void setRadioButtonInvalidValue() throws IOException
    {
        PDRadioButton radioButton = (PDRadioButton) acrobatAcroForm.getField("RadioButtonGroup");
        // Set a value which doesn't match the radio button list
        assertThrows(IllegalArgumentException.class, () -> radioButton.setValue("InvalidValue"));
    }

    @Test
    void setAbstractedRadioButtonInvalidValue() throws IOException
    {
        PDField radioButton = acrobatAcroForm.getField("RadioButtonGroup");
        // Set a value which doesn't match the radio button list
        assertThrows(IllegalArgumentException.class, () -> radioButton.setValue("InvalidValue"));
    }
    
    @AfterEach
    public void tearDown() throws IOException
    {
        document.close();
        acrobatDocument.close();
    }
    
}

