'Merge the information in `other` into self. In case of conflicts, the information
in `other` takes precedence, unless the information in `other` is NULL.'

def smart_update(self, other, replace_metadata=False):

    def copy_not_none(dest, src, attr):
        v = getattr(src, attr, None)
        if (v not in (None, NULL_VALUES.get(attr, None))):
            setattr(dest, attr, copy.deepcopy(v))
    unknown = _('Unknown')
    if (other.title and (other.title != unknown)):
        self.title = other.title
        if hasattr(other, 'title_sort'):
            self.title_sort = other.title_sort
    if (other.authors and ((other.authors[0] != unknown) or ((not self.authors) or ((len(self.authors) == 1) and (self.authors[0] == unknown) and (getattr(self, 'author_sort', None) == unknown))))):
        self.authors = list(other.authors)
        if hasattr(other, 'author_sort_map'):
            self.author_sort_map = dict(other.author_sort_map)
        if hasattr(other, 'author_sort'):
            self.author_sort = other.author_sort
    if replace_metadata:
        for attr in SC_COPYABLE_FIELDS:
            setattr(self, attr, getattr(other, attr, (1.0 if (attr == 'series_index') else None)))
        self.tags = other.tags
        self.cover_data = getattr(other, 'cover_data', NULL_VALUES['cover_data'])
        self.set_all_user_metadata(other.get_all_user_metadata(make_copy=True))
        for x in SC_FIELDS_COPY_NOT_NULL:
            copy_not_none(self, other, x)
        if callable(getattr(other, 'get_identifiers', None)):
            self.set_identifiers(other.get_identifiers())
    else:
        for attr in SC_COPYABLE_FIELDS:
            copy_not_none(self, other, attr)
        for x in SC_FIELDS_COPY_NOT_NULL:
            copy_not_none(self, other, x)
        if other.tags:
            lotags = [t.lower() for t in other.tags]
            lstags = [t.lower() for t in self.tags]
            (ot, st) = map(frozenset, (lotags, lstags))
            for t in st.intersection(ot):
                sidx = lstags.index(t)
                oidx = lotags.index(t)
                self.tags[sidx] = other.tags[oidx]
            self.tags += [t for t in other.tags if (t.lower() in (ot - st))]
        if getattr(other, 'cover_data', False):
            other_cover = other.cover_data[(-1)]
            self_cover = (self.cover_data[(-1)] if self.cover_data else '')
            if (not self_cover):
                self_cover = ''
            if (not other_cover):
                other_cover = ''
            if (len(other_cover) > len(self_cover)):
                self.cover_data = other.cover_data
        if callable(getattr(other, 'custom_field_keys', None)):
            for x in other.custom_field_keys():
                meta = other.get_user_metadata(x, make_copy=True)
                if (meta is not None):
                    self_tags = self.get(x, [])
                    self.set_user_metadata(x, meta)
                    other_tags = other.get(x, [])
                    if ((meta['datatype'] == 'text') and meta['is_multiple']):
                        lotags = [t.lower() for t in other_tags]
                        try:
                            lstags = [t.lower() for t in self_tags]
                        except TypeError:
                            lstags = []
                            self_tags = []
                        (ot, st) = map(frozenset, (lotags, lstags))
                        for t in st.intersection(ot):
                            sidx = lstags.index(t)
                            oidx = lotags.index(t)
                            self_tags[sidx] = other_tags[oidx]
                        self_tags += [t for t in other_tags if (t.lower() in (ot - st))]
                        setattr(self, x, self_tags)
        my_comments = getattr(self, 'comments', '')
        other_comments = getattr(other, 'comments', '')
        if (not my_comments):
            my_comments = ''
        if (not other_comments):
            other_comments = ''
        if (len(other_comments.strip()) > len(my_comments.strip())):
            self.comments = other_comments
        if callable(getattr(other, 'get_identifiers', None)):
            d = self.get_identifiers()
            s = other.get_identifiers()
            d.update([v for v in s.iteritems() if (v[1] is not None)])
            self.set_identifiers(d)
        else:
            for attr in TOP_LEVEL_IDENTIFIERS:
                copy_not_none(self, other, attr)
    other_lang = getattr(other, 'languages', [])
    if (other_lang and (other_lang != ['und'])):
        self.languages = list(other_lang)
    if (not getattr(self, 'series', None)):
        self.series_index = None
