/*
 * -----------------------------------------------------------------------------
 * COPYRIGHT (C) 2019, FONTEVA, INC.
 * ALL RIGHTS RESERVED.
 *
 * ALL INFORMATION CONTAINED HEREIN IS, AND REMAINS THE PROPERTY OF FONTEVA
 * INCORPORATED AND ITS SUPPLIERS, IF ANY. THE INTELLECTUAL AND TECHNICAL
 * CONCEPTS CONTAINED HEREIN ARE PROPRIETARY TO FONTEVA INCORPORATED AND
 * ITS SUPPLIERS AND MAY BE COVERED BY U.S. AND FOREIGN PATENTS, PATENTS IN
 * PROCESS, AND ARE PROTECTED BY TRADE SECRET OR COPYRIGHT LAW. DISSEMINATION
 * OF THIS INFORMATION OR REPRODUCTION OF THIS MATERIAL IS STRICTLY FORBIDDEN
 * UNLESS PRIOR WRITTEN PERMISSION IS OBTAINED FROM FONTEVA, INC.
 * -----------------------------------------------------------------------------
 */

/**
 * Useful static methods for working with Collections
 * @author avisser
 **/
public without sharing class PfmSetUtils {
    /**
     * Returns a set of ids containing the record ids for each
     * SObject contained in the list of sobjects passed in as
     * an argument
     * @param records List of records to return a set of ids for
     * @return Set<Id> Ids for each record in the list of sobjects passed in as an argument
     */
    public static Set<Id> getIds(List<SObject> records) {
        return getIds(records, 'id');
    }

    /**
     * Returns a set of ids containing the record ids for each
     * SObject contained in the list of sobjects passed in as
     * an argument
     * @param records List of records to return a set of ids for
     * @param fieldName String fieldName to extract Id from
     * @return Set<Id> Ids for each record in the list of sobjects passed in as an argument
     */
    public static Set<Id> getIds(List<SObject> records, String fieldName) {
        Set<Id> results = new Set<Id>();
        for (SObject obj : records) {
            if (obj.get(fieldName) != null)
                results.add(Id.valueOf(String.valueOf(obj.get(fieldName))));
        }
        return results;
    }

    /**
     * Returns a List of ids containing the record ids for each
     * SObject contained in the list of sobjects passed in as
     * an argument
     * @param records List of records to return a set of ids for
     * @return List<Id> Ids for each record in the list of sobjects passed in as an argument
     */
    public static List<Id> getIdList(List<SObject> records) {
        return getIdList(records, 'id');
    }

    public static List<Id> toList(Set<Id> stringSet) {
        List<Id> retVal = new List<Id>();
        for (String str : stringSet) {
            retVal.add(str);
        }
        return retVal;
    }

    public static List<String> toList(Set<String> stringSet) {
        List<String> retVal = new List<String>();
        for (String str : stringSet) {
            retVal.add(str);
        }
        return retVal;
    }

    /**
     * Returns a List of ids containing the record ids for each
     * SObject contained in the list of sobjects passed in as
     * an argument
     * @param records List of records to return a set of ids for
     * @param fieldName String fieldName to extract Id from
     * @return List<Id> Ids for each record in the list of sobjects passed in as an argument
     */
    public static List<Id> getIdList(List<SObject> records, String fieldName) {
        List<Id> results = new List<Id>();
        for (SObject obj : records) {
            if (obj.get(fieldName) != null)
                results.add(Id.valueOf(String.valueOf(obj.get(fieldName))));
        }
        return results;
    }

    /**
     * Extract a string field from a list of objects (non-SObjects)
     *
     * @param records
     * @param fieldName
     *
     * @return a list containing the field from each object
     */
    public static List<String> mapToString(List<Object> records, String fieldName) {
        List<String> results = new List<String>();
        List<Object> mapRecords = (List<Object>) JSON.deserializeUntyped(JSON.serialize(records));
        for (Object obj : mapRecords) {
            Map<String, Object> mapObj = (Map<String, Object>) obj;
            results.add((String) mapObj.get(fieldName));
        }
        return results;
    }

    /**
     * Returns the first object in the list where obj.field == value. Returns null if nothing matches
     *
     * @param objects
     * @param field
     * @param value
     *
     * @return
     */
    public static SObject find(List<SObject> objects, String field, Object value) {
        for (SObject obj : objects) {
            if (obj.get(field) == value) {
                return obj;
            }
        }
        return null;
    }

    /**
     * From the list, return the SObjects where `SObj.get(field) == value`
     *
     * @param objects
     * @param field
     * @param value
     *
     * @return
     */
    public static List<SObject> filter(List<SObject> objects, String field, Object value) {
        List<SObject> retVal = new List<SObject>();
        for (SObject obj : objects) {
            if (obj.get(field) == value) {
                retVal.add(obj);
            }
        }
        return retVal;
    }

    /**
     * From the list, return the strings that match the regEx
     *
     * @param strings
     * @param regEx
     *
     * @return
     */
    public static List<String> filterStrings(List<String> strings, String regEx) {
        List<String> retVal = new List<String>();
        Pattern p = Pattern.compile(regEx);
        for (String s : strings) {
            if (p.matcher(s).matches()) {
                retVal.add(s);
            }
        }
        return retVal;
    }

    /**
     * Return a list of trimmed strings
     *
     * @param strings
     * @param regEx
     *
     * @return
     */
    public static List<String> trim(List<String> strings) {
        List<String> retVal = new List<String>();
        for (String s : strings) {
            retVal.add(s.trim());
        }
        return retVal;
    }

    /**
     * From the list, return the non-empty strings
     *
     * @param strings
     *
     * @return
     */
    public static List<String> filterNotEmpty(List<String> strings) {
        List<String> retVal = new List<String>();
        for (String s : strings) {
            if (String.isNotEmpty(s)) {
                retVal.add(s);
            }
        }
        return retVal;
    }

    public static List<String> mapToLower(List<String> strings) {
        List<String> retVal = new List<String>();
        for (String s : strings) {
            if (String.isNotEmpty(s)) {
                retVal.add(s.toLowerCase());
            } else {
                retVal.add(s);
            }
        }
        return retVal;
    }

    /**
     * .map(), but only for strings
     *
     * @param strings
     * @param regEx The regex to match on. Strings that don't match map to `null`
     * @param replacement Include matching groups with `$1`, `$2` ...
     *
     * @return
     */
    public static List<String> mapStrings(List<String> strings, String regEx, String replacement) {
        List<String> retVal = new List<String>();
        Pattern p = Pattern.compile(regEx);
        for (String s : strings) {
            Matcher matcher = p.matcher(s);
            if (matcher.matches()) {
                String mappedStr = replacement;
                // replace $1/$2 ... with matches from matcher
                for (Integer i = 1; i < matcher.groupCount() + 1; i++) {
                    // start at 1 as group 1 is pattern match, not capture match
                    mappedStr = mappedStr.replaceAll('\\$' + i, matcher.group(i));
                }
                retVal.add(mappedStr);
            } else {
                retVal.add(null);
            }
        }
        return retVal;
    }

    /**
     * Find the first SObject with a child property equal to the provided value.
     * E.g. `.findChild(contacts, 'Account.Name', 'Foo Corp')`
     *
     * @param objects
     * @param dottedFieldName
     * @param value
     *
     * @return
     */
    public static SObject findByRelatedField(List<SObject> objects, String dottedFieldName, Object value) {
        String relation = dottedFieldName.trim().split('\\.')[0];
        String field = dottedFieldName.trim().split('\\.')[1];
        for (SObject obj : objects) {
            if (obj.getSObject(relation) != null && obj.getSObject(relation).get(field) == value) {
                return obj;
            }
        }

        return null;
    }

    /**
     * Get a child value from a list of SObjects
     *
     * @param objects
     * @param dottedFieldName
     *
     * @return
     */
    public static List<Object> mapToRelatedField(List<SObject> objects, String dottedFieldName) {
        List<Object> retVal = new List<Object>();

        String relation = dottedFieldName.trim().split('\\.')[0];
        String field = dottedFieldName.trim().split('\\.')[1];
        for (SObject obj : objects) {
            if (obj.getSObject(relation) != null) {
                retVal.add(obj.getSObject(relation).get(field));
            } else {
                retVal.add(null);
            }
        }

        return retVal;
    }

    /**
     * Cast a list of Objects to a list of Ids. This method is syntactic sugar.
     *
     * @param objects
     *
     * @return
     */
    public static List<Id> asIds(List<Object> objects) {
        List<Id> retVal = new List<Id>();
        for (Object obj : objects) {
            if (obj != null) {
                retVal.add((Id) obj);
            }
        }
        return retVal;
    }

    /**
     * Return a map where SObj.get(keyField) => SObj
     *
     * @param objects
     * @param keyField
     *
     * @return
     */
    public static Map<Id, List<SObject>> bucketById(List<SObject> objects, String keyField) {
        Map<Id, List<SObject>> retVal = new Map<Id, List<SObject>>();
        for (SObject obj : objects) {
            if (retVal.get((Id) obj.get(keyField)) == null) {
                retVal.put((Id) obj.get(keyField), new List<SObject>());
            }
            retVal.get((Id) obj.get(keyField)).add(obj);
        }
        return retVal;
    }
}
