/*******************************************************************************
 * Copyright (c) 2012, 2020 Original authors and others.
 *
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     Original authors and others - initial API and implementation
 ******************************************************************************/
package org.eclipse.nebula.widgets.nattable.dataset.person;

import java.math.BigDecimal;
import java.security.SecureRandom;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Random;

import org.eclipse.nebula.widgets.nattable.dataset.person.Person.Gender;

/**
 * Class that acts as service for accessing numerous {@link Person}s. The values
 * are randomly put together out of names and places from "The Simpsons"
 */
public final class PersonService {

    private PersonService() {
        // private default constructor for helper class
    }

    private static Random randomGenerator = new SecureRandom();

    static String[] maleNames = { "Bart", "Homer", "Lenny", "Carl", "Waylon",
            "Ned", "Timothy", "Rodd", "Todd" };
    static String[] femaleNames = { "Marge", "Lisa", "Maggie", "Edna", "Helen",
            "Jessica", "Maude" };
    static String[] lastNames = { "Simpson", "Leonard", "Carlson", "Smithers",
            "Flanders", "Krabappel", "Lovejoy" };
    static String[] streetNames = new String[] { "Evergreen Terrace",
            "Main Street", "South Street", "Plympton Street",
            "Highland Avenue", "Elm Street", "Oak Grove Street" };
    static String[] cityNames = new String[] { "Springfield", "Shelbyville",
            "Ogdenville", "Waverly Hills", "North Haverbrook", "Capital City" };
    static String[] foodList = new String[] { "Donut", "Bacon", "Fish",
            "Vegetables", "Ham", "Prezels" };
    static String[] drinkList = new String[] { "Beer", "Water", "Soda", "Milk",
            "Coke", "Fizzy Bubblech" };
    static String baseText = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, "
            + "sed diam nonumy eirmod tempor invidunt ut labore et dolore "
            + "magna aliquyam erat, sed diam voluptua.";

    /**
     * Creates a list of random {@link Person}s.
     *
     * @param numberOfPersons
     *            The number of {@link Person}s that should be generated.
     * @return A list containing the given amount of random generated persons.
     */
    public static List<Person> getRandomPersons(int numberOfPersons) {
        List<Person> result = new ArrayList<>();

        for (int i = 0; i < numberOfPersons; i++) {
            result.add(createPerson(i));
        }

        return result;
    }

    /**
     *
     * @return A fixed list of {@link Person}s.
     */
    public static List<Person> getFixedPersons() {
        List<Person> result = new ArrayList<>();

        // create 10 Simpsons
        // 3 Homer
        result.add(new Person(1, maleNames[1], lastNames[0], Gender.MALE, true, new Date(), 100d));
        result.add(new Person(2, maleNames[1], lastNames[0], Gender.MALE, true, new Date(), 100d));
        result.add(new Person(3, maleNames[1], lastNames[0], Gender.MALE, true, new Date(), 100d));
        // 3 Bart
        result.add(new Person(4, maleNames[0], lastNames[0], Gender.MALE, false, new Date(), 100d));
        result.add(new Person(5, maleNames[0], lastNames[0], Gender.MALE, false, new Date(), 100d));
        result.add(new Person(6, maleNames[0], lastNames[0], Gender.MALE, false, new Date(), 100d));
        // 2 Marge
        result.add(new Person(7, femaleNames[0], lastNames[0], Gender.FEMALE, true, new Date(), 100d));
        result.add(new Person(8, femaleNames[0], lastNames[0], Gender.FEMALE, true, new Date(), 100d));
        // 2 Lisa
        result.add(new Person(9, femaleNames[1], lastNames[0], Gender.FEMALE, false, new Date(), 100d));
        result.add(new Person(10, femaleNames[1], lastNames[0], Gender.FEMALE, false, new Date(), 100d));

        // create 8 Flanders
        // 2 Ned
        result.add(new Person(11, maleNames[5], lastNames[4], Gender.MALE, true, new Date(), 100d));
        result.add(new Person(12, maleNames[5], lastNames[4], Gender.MALE, true, new Date(), 100d));
        // 2 Maude
        result.add(new Person(13, femaleNames[6], lastNames[4], Gender.FEMALE, true, new Date(), 100d));
        result.add(new Person(14, femaleNames[6], lastNames[4], Gender.FEMALE, true, new Date(), 100d));
        // 2 Rod
        result.add(new Person(15, maleNames[7], lastNames[4], Gender.MALE, false, new Date(), 100d));
        result.add(new Person(16, maleNames[7], lastNames[4], Gender.MALE, false, new Date(), 100d));
        // 2 Tod
        result.add(new Person(17, maleNames[8], lastNames[4], Gender.MALE, false, new Date(), 100d));
        result.add(new Person(18, maleNames[8], lastNames[4], Gender.MALE, false, new Date(), 100d));

        return result;
    }

    /**
     *
     * @return A fixed list of {@link Person}s with a few null values.
     */
    public static List<Person> getFixedPersonsWithNull() {
        List<Person> result = new ArrayList<>();

        // create 5 Simpsons
        // 2 Homer
        result.add(new Person(1, maleNames[1], lastNames[0], Gender.MALE, true, new Date(), 100d));
        result.add(new Person(3, maleNames[1], lastNames[0], Gender.MALE, true, new Date(), 100d));
        // 2 Marge
        result.add(new Person(7, femaleNames[0], lastNames[0], Gender.FEMALE, true, new Date(), 100d));
        result.add(new Person(8, femaleNames[0], lastNames[0], Gender.FEMALE, true, new Date(), 100d));
        // 1 Bart without money
        result.add(new Person(7, femaleNames[0], lastNames[0], Gender.FEMALE, true, new Date(), null));

        // create 2 Flanders without last name
        // 1 Ned
        result.add(new Person(11, maleNames[5], null, Gender.MALE, true, new Date(), 100d));
        // 1 Maude
        result.add(new Person(13, femaleNames[6], null, Gender.FEMALE, true, new Date(), 100d));

        return result;
    }

    /**
     *
     * @return A fixed list of {@link Person}s with a few double values.
     */
    public static List<Person> getFixedMixedPersons() {
        List<Person> result = new ArrayList<>();

        result.add(new Person(21, maleNames[0], lastNames[2], Gender.MALE, true, new Date(), 100d));
        result.add(new Person(22, maleNames[1], lastNames[2], Gender.MALE, true, new Date(), 100d));
        result.add(new Person(23, maleNames[5], lastNames[2], Gender.MALE, true, new Date(), 100d));
        result.add(new Person(24, femaleNames[0], lastNames[2], Gender.FEMALE, false, new Date(), 100d));
        result.add(new Person(25, femaleNames[6], lastNames[2], Gender.FEMALE, false, new Date(), 100d));

        // add doubles
        result.add(new Person(30, maleNames[1], lastNames[0], Gender.MALE, true, new Date(), 100d));
        result.add(new Person(31, maleNames[1], lastNames[0], Gender.MALE, true, new Date(), 100d));
        result.add(new Person(32, maleNames[1], lastNames[2], Gender.MALE, true, new Date(), 100d));
        result.add(new Person(33, maleNames[1], lastNames[2], Gender.MALE, true, new Date(), 100d));

        return result;
    }

    /**
     * Creates a list of {@link Person}s.
     *
     * @param numberOfPersons
     *            The number of {@link Person}s that should be generated.
     * @return A list of {@link Person}s.
     */
    public static List<Person> getPersons(int numberOfPersons) {
        List<Person> result = new ArrayList<>();

        for (int i = 0; i < numberOfPersons; i++) {
            result.add(createPerson(i));
        }

        return result;
    }

    /**
     * Creates a list of {@link Address}.
     *
     * @param number
     *            The number of {@link Address} that should be generated.
     * @return A list of {@link Address}.
     */
    public static List<Address> getAddress(int number) {
        List<Address> result = new ArrayList<>();

        for (int i = 0; i < number; i++) {
            result.add(createAddress());
        }

        return result;
    }

    /**
     * Creates a list of {@link PersonWithAddress}.
     *
     * @param numberOfPersons
     *            The number of {@link PersonWithAddress} that should be
     *            generated.
     * @return A list of {@link PersonWithAddress}.
     */
    public static List<PersonWithAddress> getPersonsWithAddress(int numberOfPersons) {
        List<PersonWithAddress> result = new ArrayList<>();

        for (int i = 0; i < numberOfPersons; i++) {
            result.add(createPersonWithAddress(i));
        }

        return result;
    }

    /**
     * Creates a list of {@link ExtendedPersonWithAddress}.
     *
     * @param numberOfPersons
     *            The number of {@link ExtendedPersonWithAddress} that should be
     *            generated.
     * @return A list of {@link ExtendedPersonWithAddress}.
     */
    public static List<ExtendedPersonWithAddress> getExtendedPersonsWithAddress(int numberOfPersons) {
        List<ExtendedPersonWithAddress> result = new ArrayList<>();

        for (int i = 0; i < numberOfPersons; i++) {
            result.add(createExtendedPersonWithAddress(i));
        }

        return result;
    }

    /**
     * Creates a random person out of names which are taken from "The Simpsons"
     * and enrich them with random generated married state and birthday date.
     *
     * @param id
     *            A unique id for the person.
     * @return A random person.
     */
    private static Person createPerson(int id) {
        Person result = new Person(id);
        result.setGender(Gender.values()[randomGenerator.nextInt(2)]);

        if (result.getGender().equals(Gender.MALE)) {
            result.setFirstName(maleNames[randomGenerator.nextInt(maleNames.length)]);
        } else {
            result.setFirstName(femaleNames[randomGenerator.nextInt(femaleNames.length)]);
        }

        result.setLastName(lastNames[randomGenerator.nextInt(lastNames.length)]);
        result.setMarried(randomGenerator.nextBoolean());

        int month = randomGenerator.nextInt(12);
        int day = 0;
        if (month == 2) {
            day = randomGenerator.nextInt(28);
        } else {
            day = randomGenerator.nextInt(30);
        }
        int year = 1920 + randomGenerator.nextInt(90);

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        try {
            result.setBirthday(sdf.parse("" + year + "-" + month + "-" + day));
        } catch (ParseException e) {
            // do nothing as there should be no parse exception
        }

        result.setMoney(randomGenerator.nextDouble() * 100);
        return result;
    }

    /**
     * Creates a random address out of street names, postal codes and city names
     * which are taken from "The Simpsons" (i haven't found postal codes, so
     * here i invented some for the example)
     *
     * @return A random address.
     */
    public static Address createAddress() {
        String[] streets = getStreetNames();
        int[] plz = { 11111, 22222, 33333, 44444, 55555, 66666 };
        String[] cities = getCityNames();

        Address result = new Address();

        result.setStreet(streets[randomGenerator.nextInt(streets.length)]);
        result.setHousenumber(randomGenerator.nextInt(200));
        int cityRandom = randomGenerator.nextInt(cities.length);
        result.setPostalCode(plz[cityRandom]);
        result.setCity(cities[cityRandom]);

        return result;
    }

    /**
     * Creates a random person out of names which are taken from "The Simpsons"
     * and enrich them with random generated married state and birthday date.
     * Also adds a random address out of street names, postal codes and city
     * names which are taken from "The Simpsons" (i haven't found postal codes,
     * so here i invented some for the example)
     *
     * @param id
     *            A unique id for the person.
     * @return A random person with a random address.
     */
    public static PersonWithAddress createPersonWithAddress(int id) {
        return new PersonWithAddress(createPerson(id), createAddress());
    }

    /**
     * Creates a random person out of names which are taken from "The Simpsons"
     * and enrich them with random generated married state and birthday date.
     * Adds a random address out of street names, postal codes and city names
     * which are taken from "The Simpsons" (i haven't found postal codes, so
     * here i invented some for the example). Also adds extended information
     * like a password, a random long description text, a money balance and
     * collections of favourite food and drinks.
     *
     * @param id
     *            A unique id for the person.
     * @return A random person with a random address and some random extended
     *         information.
     */
    public static ExtendedPersonWithAddress createExtendedPersonWithAddress(int id) {
        return new ExtendedPersonWithAddress(
                createPerson(id),
                createAddress(),
                generateSimplePassword(),
                createRandomLengthText(),
                createRandomMoneyAmount(),
                createFavouriteFood(),
                createFavouriteDrinks());
    }

    /**
     * @return A simple password consisting of 8 characters in the value ranges
     *         a-z, A-Z
     */
    public static String generateSimplePassword() {
        String result = "";
        for (int i = 0; i < 7; i++) {
            int rnd = (int) (Math.random() * 52);
            char base = (rnd < 26) ? 'A' : 'a';
            result += (char) (base + rnd % 26);
        }
        return result;
    }

    private static List<String> createFavouriteFood() {
        String[] food = getFoodList();
        int favCount = randomGenerator.nextInt(food.length);

        List<String> result = new ArrayList<String>();
        for (int i = 0; i < favCount; i++) {
            int randIndex = randomGenerator.nextInt(food.length);
            if (!result.contains(food[randIndex])) {
                result.add(food[randIndex]);
            }
        }
        return result;
    }

    private static List<String> createFavouriteDrinks() {
        String[] drinks = getDrinkList();
        int favCount = randomGenerator.nextInt(drinks.length);

        List<String> result = new ArrayList<String>();
        for (int i = 0; i < favCount; i++) {
            int randIndex = randomGenerator.nextInt(drinks.length);
            if (!result.contains(drinks[randIndex])) {
                result.add(drinks[randIndex]);
            }
        }
        return result;
    }

    /**
     * @return An array of street names that are also used to create random
     *         addresses.
     */
    public static String[] getStreetNames() {
        return streetNames;
    }

    /**
     * @return An array of city names that are also used to create random
     *         addresses.
     */
    public static String[] getCityNames() {
        return cityNames;
    }

    /**
     * @return An array of food names.
     */
    public static String[] getFoodList() {
        return foodList;
    }

    /**
     * @return An array of drink names.
     */
    public static String[] getDrinkList() {
        return drinkList;
    }

    /**
     * @return A custom length text containing line breaks
     */
    public static String createRandomLengthText() {
        String[] words = baseText.split(" ");

        String msg = "";
        int randWords = randomGenerator.nextInt(words.length);
        for (int j = 0; j < randWords; j++) {
            msg += words[j];
            if (msg.endsWith(",") || msg.endsWith(".")) {
                msg += "\n";
            } else {
                msg += " ";
            }
        }

        return msg;
    }

    public static Double createRandomMoneyAmount() {
        Double result = randomGenerator.nextDouble() * 1000;
        BigDecimal bd = new BigDecimal(result);
        bd = bd.setScale(2, BigDecimal.ROUND_HALF_UP);
        return bd.doubleValue();
    }
}
