/*
 * 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.zeppelin.display;

import org.apache.zeppelin.display.ui.CheckBox;
import org.apache.zeppelin.display.ui.OptionInput.ParamOption;
import org.apache.zeppelin.display.ui.Select;
import org.apache.zeppelin.display.ui.TextBox;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;


class GUITest {

  private ParamOption[] options = new ParamOption[]{
      new ParamOption("1", "value_1"),
      new ParamOption("2", "value_2")
  };

  private List<Object> checkedItems;

  @BeforeEach
  void setUp() {
    checkedItems = new ArrayList<>();
    checkedItems.add("1");
  }

  @Test
  void testSelect() {
    GUI gui = new GUI();
    Object selected = gui.select("list_1", options, null);
    // use the first one as the default value
    assertEquals("1", selected);

    gui = new GUI();
    selected = gui.select("list_1", options,  "2");
    assertEquals("2", selected);
    // "2" is selected by above statement, so even this default value is "1", the selected value is
    // still "2"
    selected = gui.select("list_1", options, "1");
    assertEquals("2", selected);
  }

  @Test
  void testSelectWithCollectionDefault() {
    GUI gui = new GUI();
    List<String> collectionDefault = Arrays.asList("2");
    Object selected = gui.select("list_collection", options, collectionDefault);
    
    // Verify that "2" (first element of collection) is used as default
    assertEquals("2", selected);
    
    // Verify the form's default value is correctly set
    Select selectForm = (Select) gui.forms.get("list_collection");
    assertEquals("2", selectForm.getDefaultValue());
  }

  @Test
  void testSelectWithCollectionDefaultMultiElement() {
    GUI gui = new GUI();
    List<String> collectionDefault = Arrays.asList("2", "3");
    Object selected = gui.select("list_collection_multi", options, collectionDefault);

    // First element of collection should be used
    assertEquals("2", selected);

    Select selectForm = (Select) gui.forms.get("list_collection_multi");
    assertEquals("2", selectForm.getDefaultValue());
  }

  @Test
  void testSelectWithEmptyCollectionDefault() {
    GUI gui = new GUI();
    List<String> emptyDefault = Collections.emptyList();
    Object selected = gui.select("list_collection_empty", options, emptyDefault);

    // Empty collection -> null -> fallback to first option ("1")
    assertEquals("1", selected);

    Select selectForm = (Select) gui.forms.get("list_collection_empty");
    assertEquals("1", selectForm.getDefaultValue());
  }

  @Test
  void testGson() {
    GUI gui = new GUI();
    gui.textbox("textbox_1", "default_text_1");
    gui.select("select_1", options, "1");
    List<Object> list = new ArrayList<>();
    list.add("1");
    gui.checkbox("checkbox_1", options, list);

    String json = gui.toJson();
    System.out.println(json);
    GUI gui2 = GUI.fromJson(json);
    assertEquals(gui2.toJson(), json);
  }

  // Case 1. Old input forms are created in backend, in this case type is always set
  @Test
  void testOldGson_1() throws IOException {

    GUI gui = new GUI();
    gui.forms.put("textbox_1", new OldInput.OldTextBox("textbox_1", "default_text_1"));
    gui.forms.put("select_1", new OldInput.OldSelect("select_1", "1", options));
    gui.forms.put("checkbox_1",
        new OldInput.OldCheckBox("checkbox_1", checkedItems, options));

    // convert to old json format.
    String json = gui.toJson();

    // convert to new input forms
    GUI gui2 = GUI.fromJson(json);
    assertEquals(3, gui2.forms.size());
    assertTrue(gui2.forms.get("textbox_1") instanceof TextBox);
    assertEquals("default_text_1", gui2.forms.get("textbox_1").getDefaultValue());
    assertTrue(gui2.forms.get("select_1") instanceof Select);
    assertArrayEquals(options, ((Select) gui2.forms.get("select_1")).getOptions());
    assertTrue(gui2.forms.get("checkbox_1") instanceof CheckBox);
    assertArrayEquals(options, ((CheckBox) gui2.forms.get("checkbox_1")).getOptions());
  }

  // Case 2. Old input forms are created in frontend, in this case type is only set for checkbox
  // Actually this is a bug due to method Input#getInputForm
  @Test
  void testOldGson_2() throws IOException {

    GUI gui = new GUI();
    gui.forms.put("textbox_1", new OldInput("textbox_1", "default_text_1"));
    gui.forms.put("select_1", new OldInput("select_1", "1", options));
    gui.forms.put("checkbox_1",
        new OldInput.OldCheckBox("checkbox_1", checkedItems, options));

    // convert to old json format.
    String json = gui.toJson();

    // convert to new input forms
    GUI gui2 = GUI.fromJson(json);
    assertEquals(3, gui2.forms.size());
    assertTrue(gui2.forms.get("textbox_1") instanceof TextBox);
    assertEquals("default_text_1", gui2.forms.get("textbox_1").getDefaultValue());
    assertTrue(gui2.forms.get("select_1") instanceof Select);
    assertArrayEquals(options, ((Select) gui2.forms.get("select_1")).getOptions());
    assertTrue(gui2.forms.get("checkbox_1") instanceof CheckBox);
    assertArrayEquals(options, ((CheckBox) gui2.forms.get("checkbox_1")).getOptions());
  }

  // load old json file and will convert it into new forms of Input
  @Test
  void testOldGson_3() throws IOException {
    String oldJson = "{\n" +
        "        \"params\": {\n" +
        "          \"maxAge\": \"35\"\n" +
        "        },\n" +
        "        \"forms\": {\n" +
        "          \"maxAge\": {\n" +
        "            \"name\": \"maxAge\",\n" +
        "            \"defaultValue\": \"30\",\n" +
        "            \"hidden\": false\n" +
        "          }\n" +
        "        }\n" +
        "      }";
    GUI gui = GUI.fromJson(oldJson);
    assertEquals(1, gui.forms.size());
    assertTrue(gui.forms.get("maxAge") instanceof TextBox);
    assertEquals("30", gui.forms.get("maxAge").getDefaultValue());

    oldJson = "{\n" +
        "        \"params\": {\n" +
        "          \"marital\": \"single\"\n" +
        "        },\n" +
        "        \"forms\": {\n" +
        "          \"marital\": {\n" +
        "            \"name\": \"marital\",\n" +
        "            \"defaultValue\": \"single\",\n" +
        "            \"options\": [\n" +
        "              {\n" +
        "                \"value\": \"single\"\n" +
        "              },\n" +
        "              {\n" +
        "                \"value\": \"divorced\"\n" +
        "              },\n" +
        "              {\n" +
        "                \"value\": \"married\"\n" +
        "              }\n" +
        "            ],\n" +
        "            \"hidden\": false\n" +
        "          }\n" +
        "        }\n" +
        "      }";
    gui = GUI.fromJson(oldJson);
    assertEquals(1, gui.forms.size());
    assertTrue(gui.forms.get("marital") instanceof Select);
    assertEquals("single", gui.forms.get("marital").getDefaultValue());
  }
}
