/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package br.com.ctecinf.autocomplete;

import br.com.ctecinf.orm.Model;
import br.com.ctecinf.swing.ListModel;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.ConcurrentModificationException;
import java.util.List;
import java.util.Map;

/**
 *
 * @author Cássio Conceição
 * @param <T>
 * @since 03/07/2019
 * @version 201907
 * @see http://ctecinf.com.br/
 */
public class AutoCompleteModel<T> extends ListModel<T> {

    public static int NUM_RESULT_SHOW = 8;

    private List<T> list;
    private boolean isLoaded = false;

    public AutoCompleteModel() {
        this(new ArrayList());
    }

    public AutoCompleteModel(List<T> list) {
        this.list = Collections.synchronizedList(new ArrayList(list));
    }

    public boolean isLoaded() {
        return isLoaded;
    }

    public void endLoad() {
        isLoaded = true;
    }

    public void addToSearch(T item) {
        getData().add(item);
    }

    @Override
    public void add(T item) {
        super.add(item);
        list.add(item);
        fireIntervalAdded(this, list.size() - 1, list.size() - 1);
    }

    @Override
    public void removeAll() {
        if (list != null && !list.isEmpty()) {
            fireIntervalRemoved(this, 0, list.size() - 1);
            list = Collections.synchronizedList(new ArrayList());
        }
    }

    public boolean isEmpty() {
        return list.isEmpty();
    }

    @Override
    public int getSize() {
        return list.size();
    }

    @Override
    public T getElementAt(int index) {
        return list.get(index);
    }

    public void filter(String value) {

        removeAll();

        for (T c : search(value)) {
            add(c);
        }
    }

    private List<T> search(String value) {

        final List<T> founds = new ArrayList();

        try {

            for (T obj : getData()) {

                if (check(obj, value)) {
                    founds.add(obj);
                }

                if (founds.size() == NUM_RESULT_SHOW) {

                    Collections.sort(founds, new Comparator<T>() {

                        @Override
                        public int compare(T o1, T o2) {

                            if (o1 == null) {
                                return -1;
                            }

                            if (o2 == null) {
                                return 1;
                            }

                            return o1.toString().compareToIgnoreCase(o2.toString());
                        }
                    });

                    return founds;
                }
            }

        } catch (ConcurrentModificationException ex) {
        }

        return founds;
    }

    private boolean check(Object obj, String value) {

        if (obj == null) {
            return false;
        }

        if (value == null) {
            return false;
        }

        boolean check = false;

        if (obj instanceof Map) {

            if (value.substring(0, 1).equalsIgnoreCase("%")) {

                for (Object vl : ((Map) obj).values()) {
                    if (vl != null && vl.toString().toLowerCase().indexOf(value.substring(1).toLowerCase()) > 0) {
                        check = true;
                        break;
                    }
                }

            } else {

                for (Object vl : ((Map) obj).values()) {
                    if (vl != null && vl.toString().toLowerCase().indexOf(value.toLowerCase()) == 0) {
                        check = true;
                        break;
                    }
                }
            }

        } else if (obj instanceof Model) {

            if (value.substring(0, 1).equalsIgnoreCase("%")) {

                if (obj.getClass().getSuperclass().isAssignableFrom(Model.class)) {

                    try {

                        java.lang.reflect.Field field = obj.getClass().getSuperclass().getDeclaredField("id");

                        field.setAccessible(true);

                        Object vl = field.get(obj);

                        if (vl != null && vl.toString().toLowerCase().indexOf(value.substring(1).toLowerCase()) > 0) {
                            check = true;
                        }

                    } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException ex) {
                        System.err.println(ex);
                    }

                }

                if (!check) {

                    for (java.lang.reflect.Field field : obj.getClass().getDeclaredFields()) {

                        field.setAccessible(true);

                        try {

                            Object vl = field.get(obj);

                            if (vl != null && vl.toString().toLowerCase().indexOf(value.substring(1).toLowerCase()) > 0) {
                                check = true;
                                break;
                            }
                        } catch (IllegalArgumentException | IllegalAccessException ex) {
                            System.err.println(ex);
                        }
                    }
                }

            } else {

                if (obj.getClass().getSuperclass().isAssignableFrom(Model.class)) {

                    try {

                        java.lang.reflect.Field field = obj.getClass().getSuperclass().getDeclaredField("id");

                        field.setAccessible(true);

                        Object vl = field.get(obj);

                        if (vl != null && vl.toString().toLowerCase().indexOf(value.toLowerCase()) == 0) {
                            check = true;
                        }

                    } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException ex) {
                        System.err.println(ex);
                    }

                }

                if (!check) {

                    for (java.lang.reflect.Field field : obj.getClass().getDeclaredFields()) {

                        field.setAccessible(true);

                        try {

                            Object vl = field.get(obj);

                            if (vl != null && vl.toString().toLowerCase().indexOf(value.toLowerCase()) == 0) {
                                check = true;
                                break;
                            }
                        } catch (IllegalArgumentException | IllegalAccessException ex) {
                            System.err.println(ex);
                        }
                    }
                }
            }
        } else if (value.substring(0, 1).equalsIgnoreCase("%")) {
            return obj.toString().toLowerCase().indexOf(value.substring(1).toLowerCase()) > 0;
        } else {
            return obj.toString().toLowerCase().indexOf(value.toLowerCase()) == 0;
        }

        return check;
    }
}
