public without sharing class SObjectHelper {
    
    /**
     * Copy every field values from from_sobj to to_sobj, if the field is in to_sobj and the value is not null
     */
    public static void MergeValue(Sobject to_sobj,Sobject from_sobj)
    {
        CopyTo(to_sobj,from_sobj,true);
    }

    /**
     * Copy every field values from from_sobj to to_sobj
     */
    public static void OverwriteValue(Sobject to_sobj,Sobject from_sobj)
    {
        CopyTo(to_sobj,from_sobj,false);
    }

    static void CopyTo(Sobject to_sobj,Sobject from_sobj, Boolean skip_from_null)
    {
        Map<String, Schema.SObjectField> to_sobj_map = GetFields(to_sobj.getSObjectType().getDescribe().getName());
        Map<String, Schema.SObjectField> from_sobj_map = GetFields(from_sobj.getSObjectType().getDescribe().getName());
        for (string field : from_sobj_map.keySet()) {

            object obj = from_sobj.get(field);
            object obj1 = to_sobj.get(field);

            if (obj != obj1 && to_sobj_map.containsKey(field) && to_sobj_map.get(field).getDescribe().isUpdateable()) {
                
                if (obj != null || !skip_from_null) {
                    System.debug('obj='+obj);
                    System.debug('obj1='+obj1);
                    to_sobj.put(field, obj);
                }
            }
        }
    }




    private static Map<string,Map<String, Schema.SObjectField>> sobject_field_map = new Map<string,Map<String, Schema.SObjectField>>();

    public static Map<String, Schema.SObjectField> GetFieldMap(string obj_name)
    {
        Map<String, Schema.SObjectField> fm = null;
        if(string.isBlank(obj_name))return fm;

        if(sobject_field_map.containsKey(obj_name))
        {
            fm = sobject_field_map.get(obj_name);
        }
        else
        {
            fm = GetFields(obj_name);
            if (fm != null) {
                sobject_field_map.put(obj_name, fm);
            }
        }
        return fm;
    }
/*
    private static Map<string,Set<string>> sobject_common_fields = new Map<string,Set<string>>();

    // get sobject fields that not be assigned in FieldPermission, they were stardard fields
    public static Set<string> GetCommonFields(string obj_name)
    {
        Set<string> fields = null;
        if(string.isBlank(obj_name))return fields;

        if(sobject_common_fields.containsKey(obj_name))
        {
            fields = sobject_common_fields.get(obj_name);
        }
        else
        {
            Map<String, Schema.SObjectField> all_fields = GetFieldMap(obj_name);
            Id profile_id = UserUtility.GetSysAdminProfileId();
            List<FieldPermissions> lfp = [SELECT Id, ParentId, SobjectType, Field, PermissionsEdit, PermissionsRead, SystemModstamp FROM FieldPermissions
            where parentId in (select id from permissionset where PermissionSet.Profile.Id = :profile_id) and sobjecttype = :obj_name ];
            Set<string> admin_s = new Set<string>();
            
            for(FieldPermissions fp : lfp)
            {
                if(fp.PermissionsRead)
                {
                    admin_s.add(fp.Field.replace(fp.SobjectType+'.',''));
                }
            }
            
            // if a field not in admin's permission, the field is a common(standard) field
            for (string f : all_fields.keySet()) {
                if (!admin_s.contains(f) && !f.endsWith('__c')) {
                    if (fields == null) {
                        fields = new Set<string>();
                    }
                    fields.add(f);
                }
            }

            if (fields != null) {
                sobject_common_fields.put(obj_name, fields);
            }
        }
        return fields;
    }

    public static Set<string> GetCurrentProfileAccessableFields(string obj_name)
    {
        return GetProfileAccessableFields(UserInfo.getProfileId(), obj_name);
    }

    private static Map<Id,Map<string,List<FieldPermissions>>> profile_field_permission_buffer = new Map<Id,Map<string,List<FieldPermissions>>>();

    public static Set<string> GetProfileAccessableFields(Id profile_id, string obj_name)
    {
        List<FieldPermissions> lfp = null;
        if(!profile_field_permission_buffer.containsKey(profile_id) || !profile_field_permission_buffer.get(profile_id).containsKey(obj_name)){
            lfp = [SELECT Id, ParentId, SobjectType, Field, PermissionsEdit, PermissionsRead, SystemModstamp FROM FieldPermissions
                                        where parentId in (select id from permissionset where PermissionSet.Profile.Id = :profile_id) and sobjecttype = :obj_name ];
            system.debug(string.format('profile {0} object {1} updated ', new object[]{profile_id,obj_name,lfp.size()}));

            Map<string,List<FieldPermissions>> temp = null;
            if(profile_field_permission_buffer.containsKey(profile_id))
            {
                temp = profile_field_permission_buffer.get(profile_id);
            }
            else{
                temp = new Map<string,List<FieldPermissions>>();
                profile_field_permission_buffer.put(profile_id,temp);
            }
            
            temp.put(obj_name,lfp);
        }
        else{
            lfp = profile_field_permission_buffer.get(profile_id).get(obj_name);
        }
        Set<string> ls = new Set<string>();
        for(FieldPermissions fp : lfp)
        {
            if(fp.PermissionsRead)
            {
                ls.add(fp.Field.replace(fp.SobjectType+'.',''));
            }
        }

        Set<string> css = GetCommonFields(obj_name);
        if (css != null) {
            ls.addAll(css);
        }

        return ls;
    }*/

    public static Map<string,FieldInfo> GetFieldInfos(string obj_name)
    {
        Map<string,Schema.SObjectField> fields_map = SObjecthelper.GetFieldMap(obj_name);
        if(fields_map == null)
        {
            return null;
        }
        Map<string,FieldInfo> res = new Map<string,FieldInfo>();
        Map<string,FieldInfo> result = new Map<string,FieldInfo>();
        FieldInfo temp = null;
        for(string s : fields_map.keySet())
        {

            Schema.DescribeFieldResult dfr = fields_map.get(s).getDescribe();
            temp = new FieldInfo();
            temp.Name = dfr.getName();
            temp.Label = dfr.getLabel();
            
            // resolve 'Business Phone' bug
            if(obj_name.toLowerCase() == 'contact' && temp.Name =='Phone' && temp.Label =='Business Phone'){
                temp.Label = 'Phone';
            }
            
            Schema.DisplayType dt = dfr.getType();
            temp.TypeEnumName = dt.name();
            
            
            //temp.DisplayType = dt;
            if((dt == schema.DisplayType.MULTIPICKLIST) || (dt == schema.DisplayType.PICKLIST) )
            {
                temp.Options = new List<Option>();
                List<Schema.PicklistEntry> pick_list_values = dfr.getPickListValues();
                for (Schema.PicklistEntry a : pick_list_values) {
                    if (!a.isActive()) {
                        continue;
                    }

                    Option lv = new Option();
                    lv.label = a.getLabel();
                    lv.value = a.getValue();
                    temp.Options.add(lv);  
                }
            }
            
            if(dt == schema.DisplayType.REFERENCE )
            {
                temp.References = new List<Option>();
                for(Schema.SObjectType reference : dfr.getReferenceTo()) {
                    System.debug('Lookup reference object name: ' + reference.getDescribe().getName());
                    System.debug('Lookup reference object label: ' + reference.getDescribe().getLabel());
                    Option lv = new Option();
                    lv.label = reference.getDescribe().getLabel();
                    lv.value = reference.getDescribe().getName();
                    temp.References.add(lv);  
                }
                List<Schema.PicklistEntry> pick_list_values = dfr.getPickListValues();
                for (Schema.PicklistEntry a : pick_list_values) {
                    if (!a.isActive()) {
                        continue;
                    }

                    
                }
            }
            
            res.put(s,temp);
        }
        
        return res;
    }
    
    public static Map<String, Schema.SObjectField> GetFields(Sobject sobj ){
        return GetFields(sobj.getSObjectType().getDescribe().getName());
    }
    
    public static Map<String, Schema.SObjectField> GetFields(string obj_name )
    {
        Map<String, Schema.SObjectType> mss = Schema.getGlobalDescribe();
        if(mss.containsKey(obj_name)){
            return Transform(mss.get(obj_name).getDescribe().fields.getMap());
        }
        else{
            return null;
        }
    }
    
    public static Map<String, Schema.FieldSet> GetFieldSets(string obj_name )
    {
        Map<String, Schema.SObjectType> mss = Schema.getGlobalDescribe();
        if(!mss.containsKey(obj_name))return null;
        Map<String, Schema.FieldSet> mssfs = mss.get(obj_name).getDescribe().fieldSets.getMap();
        return mssfs;
    }
    
    public static Map<String, Schema.FieldSetMember> GetFieldSet(string obj_name,string field_set_name )
    {
        Map<String, Schema.FieldSet> mssfs = GetFieldSets(obj_name);
        if(!mssfs.containsKey(field_set_name))return null;
        Map<String, Schema.FieldSetMember> result = new Map<String, Schema.FieldSetMember>();
        for(Schema.FieldSetMember member : mssfs.get(field_set_name).getFields()){
            Schema.SObjectField ssf = member.getSObjectField();
            result.put(ssf.getDescribe().getName(),member);
        }
        return result;
    }
    
    static Map<String, Schema.SObjectField> Transform(Map<String, Schema.SObjectField> mssof){
        if(mssof == null)return null;
        Map<String, Schema.SObjectField> result = new Map<String, Schema.SObjectField>();
        for(string key : mssof.keySet()){
            result.put(mssof.get(key).getDescribe().getName(),mssof.get(key));
        }
        return result;
    }
    /*
    static Map<String, Schema.FieldSet> Transform(Map<String, Schema.FieldSet> mssfs){
        if(mssfs == null)return null;
        Map<String, Schema.FieldSet> result = new Map<String, Schema.FieldSet>();
        for(string key : mssfs.keySet()){
            result.put(mssfs.get(key).getName(),mssfs.get(key));
        }
        return result;
    }*/
    
    public static Map<string,string> GetPicklistLabelValueMap(string objectName, string fieldName){
        
        List<Schema.PicklistEntry> temp = GetPicklistEntryList(objectName,fieldName);
        if(temp == null)return null;
        
        Map<string,string> res = new Map<string,string>();
        
        for(Schema.PicklistEntry pe : temp){
            res.put(pe.getLabel(),pe.getValue());
        }
        return res;
    }
    
    public static Map<string,string> GetPicklistValueLabelMap(string objectName, string fieldName){
        
        List<Schema.PicklistEntry> temp = GetPicklistEntryList(objectName,fieldName);
        if(temp == null)return null;
        
        Map<string,string> res = new Map<string,string>();
        
        for(Schema.PicklistEntry pe : temp){
            res.put(pe.getValue(),pe.getLabel());
        }
        return res;
    }
    
    public static List<Schema.PicklistEntry> GetPicklistEntryList(string objectName, string fieldName){
        
        Map<String,String> picklistMap = new Map<String,String>();
        Schema.SObjectType targetSobjectType = Schema.getGlobalDescribe().get(objectName);
        Schema.DescribeSObjectResult objectDescribe = targetSobjectType.getDescribe(); 
        Map<String, Schema.SObjectField> fieldMap = objectDescribe.fields.getMap();
        if(!fieldMap.containsKey(fieldName))return null;
        List<Schema.PicklistEntry> picklistEntries = fieldMap.get(fieldName).getDescribe().getPickListValues(); 
        return picklistEntries;
    }
        
	
    
    
    
    
    public static string GetFieldDescription(string object_name, string field_name){

        if(string.isBlank(object_name) || String.isBlank(field_name))return null;

        // List<FieldDefinition> fd = [Select Label, QualifiedApiName, Description from FieldDefinition Where EntityDefinition.QualifiedApiName=:object_name and QualifiedApiName=:field_name];
        List<FieldDefinition> fds = GetFieldDefinitions(object_name);
        for(FieldDefinition fd : fds)
        {
            if(fd.QualifiedApiName == field_name){
                return fd.Description;
            }
        }
        return null;
    }
    
    public static List<FieldDefinition> GetFieldDefinitions(string object_name){
        return [Select Label, QualifiedApiName, Description from FieldDefinition Where EntityDefinition.QualifiedApiName=:object_name];
    }
    
    /*
    public static Schema.FieldSet GetFieldSet(string obj_name,string fieldset_name )
    {
        Map<String, Schema.FieldSet> msf = GetFieldSets(obj_name);
        if(msf.containsKey(fieldset_name))return msf.get(fieldset_name);
        return null;
    }
    
    public static Schema.FieldSet GetFieldSetByName(string obj_name,string fieldset_name )
    {
        Map<String, Schema.FieldSet> msf = GetFieldSets(obj_name);
        for(string key : msf.keyset())
        {
            if(msf.get(key).getName() == fieldset_name )
            {
                return msf.get(key);
            }
        }
        return null;
    }*/
    

    /**
     * get sobject field value, support forign field if it was queried
     * e.g. GetSObjectField(lead_obj,'Owner.Name')
     */
    public static object GetSObjectField(object o,string f_str){
        return GetSObjectField((sobject)o,f_str);
    }
    
    /**
     * get sobject field value, support forign field if it was queried
     * e.g. GetSObjectField(lead_obj,'Owner.Name')
     */
    public static object GetSObjectField(sobject o,string f_str){
        List<string> fields = f_str.split('\\.');
        //system.debug('fields = ' + fields);
        sobject temp = o;
        Object result = null;
        for(integer i =0; i < fields.size();i++){
            
            if(temp == null) return null;

            if(i==fields.size()-1){
                result = temp.get(fields[i]);
            }
            else{
                temp = temp.getSObject(fields[i]);
            }
            
            /*
            if(result instanceof SObject){
                temp = (SObject)result;
                continue;
            }
            break;*/
        }
        
        return result;
    }
    
    /*
    public static Schema.FieldSet GetFieldSetByLabel(string obj_name,string fieldset_lable )
    {
        Map<String, Schema.FieldSet> msf = GetFieldSets(obj_name);
        for(string key : msf.keyset())
        {
            if(msf.get(key).getLabel() == fieldset_lable )
            {
                return msf.get(key);
            }
        }
        return null;
    }*/

    public static List<string> GetMultiPickListOptions(string val){
        return GetOptions(val);
    }
	
    public static List<string> GetOptions(string val){
        if(val == null)return new List<string>();
            return ValidMultiPickListValue(val).split(';');
    }
    
    public static string AppendValueToMultiPickList(string old_val,string val){
        return ValidMultiPickListValue(old_val+';'+val);
    }
    
    public static string ValidMultiPickListValue( string val ){
        if(string.isBlank(val))return val;
        return ValidMultiPickListValue(val.split(';'));
    }
    
    public static string ValidMultiPickListValue( List<string> vals ){
        if(vals == null ) vals = new List<string>();
        Set<string> ss = new Set<string>(vals);
        List<string> ls = new List<string>();
        for(string s : ss){
            if(string.isBlank(s)){
                continue;
            }
            ls.add(s);
        }
        return string.join(ls, ';');
    }
}