Single<[StoreVO]> { return Single.create { single in let storeValue = self.realm.objects(StoreTable.self) .sorted(byKeyPath: "bookmarkDate", ascending: sortAscending) .filter("bookmark == %@", true) .filter { [weak self] storeValue in return self?.filterStoreTable(storeTable: storeValue, with: categoryFilter) ?? true } .map { $0.toDomain() } as [StoreVO] single(.success(st"> Single<[StoreVO]> { return Single.create { single in let storeValue = self.realm.objects(StoreTable.self) .sorted(byKeyPath: "bookmarkDate", ascending: sortAscending) .filter("bookmark == %@", true) .filter { [weak self] storeValue in return self?.filterStoreTable(storeTable: storeValue, with: categoryFilter) ?? true } .map { $0.toDomain() } as [StoreVO] single(.success(st"> Single<[StoreVO]> { return Single.create { single in let storeValue = self.realm.objects(StoreTable.self) .sorted(byKeyPath: "bookmarkDate", ascending: sortAscending) .filter("bookmark == %@", true) .filter { [weak self] storeValue in return self?.filterStoreTable(storeTable: storeValue, with: categoryFilter) ?? true } .map { $0.toDomain() } as [StoreVO] single(.success(st">
import Foundation

import RxSwift
import RealmSwift

final class DefaultRealmRepository: RealmRepository {
    private let realm: Realm
    
    init?() {
        guard let realm = try? Realm() else { return nil }
        self.realm = realm
        
        if let fileURL = realm.configuration.fileURL {
            print("Realm fileURL \\(String(describing: fileURL))")
        }
    }
    
    func fetchBookmarkStore(sortAscending: Bool, categoryFilter: CategoryFilterType) -> Single<[StoreVO]> {
        return Single.create { single in
            let storeValue = self.realm.objects(StoreTable.self)
                .sorted(byKeyPath: "bookmarkDate", ascending: sortAscending)
                .filter("bookmark == %@", true)
                .filter { [weak self] storeValue in
                    return self?.filterStoreTable(storeTable: storeValue, with: categoryFilter) ?? true
                }
                .map { $0.toDomain() } as [StoreVO]
            
            single(.success(storeValue))
            return Disposables.create()
        }
    }
        
    func fetchStoreSortedByRating(sortAscending: Bool, categoryFilter: CategoryFilterType) -> Single<[StoreVO]> {
        return Single.create { single in
            let sortProperties = [SortDescriptor(keyPath: "rate", ascending: sortAscending),
                                  SortDescriptor(keyPath: "date", ascending: false)]
            
            let storeValue = self.realm.objects(StoreTable.self)
                .sorted(by: sortProperties)
                .filter("rate != %@", 0)
                .filter { [weak self] storeValue in
                    return self?.filterStoreTable(storeTable: storeValue, with: categoryFilter) ?? true
                }
                .map { $0.toDomain() } as [StoreVO]
            
            single(.success(storeValue))
            return Disposables.create()
        }
    }
    
    func fetchBookmarkStoreSortedByRating(sortAscending: Bool, categoryFilter: CategoryFilterType) -> Single<[StoreVO]> {
        return Single.create { single in
            let sortProperties = [SortDescriptor(keyPath: "rate", ascending: sortAscending),
                                  SortDescriptor(keyPath: "date", ascending: false)]
            
            let storeValue = self.realm.objects(StoreTable.self)
                .sorted(by: sortProperties)
                .filter("rate != %@", 0)
                .filter("bookmark == %@", true)
                .filter { [weak self] storeValue in
                    return self?.filterStoreTable(storeTable: storeValue, with: categoryFilter) ?? true
                }
                .map { $0.toDomain() } as [StoreVO]
            
            single(.success(storeValue))
            return Disposables.create()
        }
    }
    
    func fetchStoreSortedByEpisodeCount(sortAscending: Bool, categoryFilter: CategoryFilterType) -> Single<[StoreVO]> {
        return Single.create { single in
            let storeValue = self.realm.objects(StoreTable.self)
                .sorted(byKeyPath: "date", ascending: false)
                .filter { storeTable in
                         return storeTable.episode.count > 0
                     }
                .sorted { (store1, store2) in
                    let episodeCount1 = store1.episode.count
                    let episodeCount2 = store2.episode.count
                    
                    if sortAscending {
                        return episodeCount1 < episodeCount2
                    } else {
                        return episodeCount1 > episodeCount2
                    }
                }
                .filter { [weak self] storeValue in
                    return self?.filterStoreTable(storeTable: storeValue, with: categoryFilter) ?? true
                }
                .map { $0.toDomain() } as [StoreVO]
            
            single(.success(storeValue))
            return Disposables.create()
        }
    }
    
    func fetchBookmarkStoreSortedByEpisodeCount(sortAscending: Bool, categoryFilter: CategoryFilterType) -> Single<[StoreVO]> {
        return Single.create { single in
            let storeValue = self.realm.objects(StoreTable.self)
                .sorted(byKeyPath: "date", ascending: false)
                .filter("bookmark == %@", true)
                .filter { storeTable in
                         return storeTable.episode.count > 0
                     }
                .sorted { (store1, store2) in
                    let episodeCount1 = store1.episode.count
                    let episodeCount2 = store2.episode.count
                    
                    if sortAscending {
                        return episodeCount1 < episodeCount2
                    } else {
                        return episodeCount1 > episodeCount2
                    }
                }
                .filter { [weak self] storeValue in
                    return self?.filterStoreTable(storeTable: storeValue, with: categoryFilter) ?? true
                }
                .map { $0.toDomain() } as [StoreVO]
            
            single(.success(storeValue))
            return Disposables.create()
        }
    }
    
    func fetchStoreSortedByName(sortAscending: Bool, categoryFilter: CategoryFilterType) -> Single<[StoreVO]> {
        return Single.create { single in
            let storeValue = self.realm.objects(StoreTable.self)
                .sorted(byKeyPath: "placeName", ascending: sortAscending)
                .filter { [weak self] storeValue in
                    return self?.filterStoreTable(storeTable: storeValue, with: categoryFilter) ?? true
                }
                .map { $0.toDomain() } as [StoreVO]
            
            single(.success(storeValue))
            return Disposables.create()
        }
    }
    
    func createStore(_ store: StoreVO) -> Completable {
        return Completable.create { completable in
            do {
                try self.realm.write {
                    self.realm.add(store.makeStoreTable())
                }
                completable(.completed)
            } catch let error {
                completable(.error(error))
            }
            return Disposables.create()
        }
    }
    
    func createStoreTable(_ store: StoreTable) -> Completable {
        return Completable.create { completable in
            do {
                try self.realm.write {
                    self.realm.add(store)
                }
                completable(.completed)
            } catch let error {
                completable(.error(error))
            }
            return Disposables.create()
        }
    }
    
    func updateStore(_ store: StoreVO) -> Completable {
        guard let storeObject = realm.object(
            ofType: StoreTable.self,
            forPrimaryKey: store.id
        ) else { return .empty() }
        
        return Completable.create { completable in
            do {
                try self.realm.write {
                    storeObject.bookmark = store.bookmark
                    storeObject.bookmarkDate = store.bookmarkDate
                    storeObject.rate = store.rate
                    storeObject.categoryType = store.categoryType
                    self.realm.add(storeObject, update: .modified)
                }
                completable(.completed)
            } catch let error {
                completable(.error(error))
            }
            return Disposables.create()
        }
    }
    
    // willDisplayCell에서 그려질 셀에 대해 필터링을 진행하는 메서드 ex) 다른뷰 이동 후 다시 재진입 시
    func updateStoreCellObservable(index: Int, storeList: [StoreVO]) -> Single<StoreVO> {
        return Single.create { single in
            var store = storeList[index]
            
            do {
                try self.realm.write {
                    let storeTable = self.realm.objects(StoreTable.self).where {
                        $0.id == store.id
                    }.first
                    
                    if let storeTable {
                        store.bookmark = storeTable.bookmark
                        store.rate = storeTable.rate
                    } else {
                        store.bookmark = false
                        store.rate = 0
                    }
                }
                
                single(.success(store))
            } catch let error {
                single(.failure(error))
            }
            return Disposables.create()
        }
    }
    
    // rx.modelSelected, rx.items에서 현재 그려진 셀에 대해 필터링을 진행하는 메서드
    func updateStoreCell(store: StoreVO) -> StoreVO? {
        do {
            var updatedStore = store
            
            try realm.write {
                if let storeTable = realm.objects(StoreTable.self).filter("id == %@", updatedStore.id).first {
                    updatedStore.bookmark = storeTable.bookmark
                    updatedStore.bookmarkDate = storeTable.bookmarkDate
                    updatedStore.rate = storeTable.rate
                }
            }
            
            return updatedStore
        } catch {
            print("Error updating store item: \\(error)")
            return nil
        }
    }
    
    // 기존에 있던 store에 에피소드를 append
    func updateEpisode(id: String, episode: EpisodeTable) -> Completable {
        guard let storeObject = realm.object(
            ofType: StoreTable.self,
            forPrimaryKey: id
        ) else { return .empty() }
        
        return Completable.create { completable in
            do {
                try self.realm.write {
                    storeObject.episode.append(episode)
                    self.realm.add(storeObject, update: .modified)
                }
                completable(.completed)
            } catch let error {
                completable(.error(error))
            }
            return Disposables.create()
        }
    }
    
    // WriteEpisode -> LocationDetail로 Dismiss될 때 LocationDetail의 viewWillAppear에서 Episode 정보를 업데이트
    func updateStoreEpisode(store: StoreVO) -> StoreVO? {
        do {
            var updatedStore = store
            
            try realm.write {
                if let storeTable = realm.objects(StoreTable.self).filter("id == %@", updatedStore.id).first {
                    
                    var episodeVOList = [EpisodeVO]()
                    
                    let episodeVOArray = storeTable.episode.map { storeObject in
                        let episodeVO = EpisodeVO(
                            id: storeObject._id.stringValue,
                            date: storeObject.date,
                            comment: storeObject.comment,
                            imageURL: storeObject.imageURL,
                            alcohol: storeObject.alcohol,
                            drink: storeObject.drink,
                            drinkQuantity: storeObject.drinkQuantity)
                        
                        return episodeVO
                    }
                    
                    episodeVOList.append(contentsOf: episodeVOArray)
                    updatedStore.episode = episodeVOList
                }
            }
            
            return updatedStore
        } catch {
            print("Error updating store item: \\(error)")
            return nil
        }
    }
    
    func deleteEpisode(id: String, episodeId: String) -> Completable {
        guard let storeObject = realm.object(
            ofType: StoreTable.self,
            forPrimaryKey: id
        ) else { return .empty() }
        
        return Completable.create { completable in
            do {
                try self.realm.write {
                    if let objectId = try? ObjectId(string: episodeId), let episodeTable = self.realm.objects(EpisodeTable.self).filter("_id == %@", objectId).first {
                        if let index = storeObject.episode.firstIndex(of: episodeTable) {
                            storeObject.episode.remove(at: index)
                            self.realm.add(storeObject, update: .modified)
                        }
                    }
                }
                completable(.completed)
            } catch let error {
                completable(.error(error))
            }
            return Disposables.create()
        }
    }
    
    func deleteStore(_ store: StoreVO) -> Completable {
        guard let storeObject = realm.object(
            ofType: StoreTable.self,
            forPrimaryKey: store.id
        ) else { return .empty() }
        
        return Completable.create { completable in
            do {
                try self.realm.write {
                    self.realm.delete(storeObject)
                }
                completable(.completed)
            } catch let error {
                completable(.error(error))
            }
            return Disposables.create()
        }
    }
    
    func checkContainsStore(id: String) -> Bool {
        let result = realm.objects(StoreTable.self).filter("id == %@", id)
        return !result.isEmpty
    }
    
    func shouldUpdateStore(_ store: StoreVO) -> Bool {
        guard let storeObject = realm.object(
            ofType: StoreTable.self,
            forPrimaryKey: store.id
        ) else { return false }
        
        if store.rate != storeObject.rate || store.bookmark != storeObject.bookmark || store.bookmarkDate != storeObject.bookmarkDate {
            return true
        }
        
        return false
    }
}

extension DefaultRealmRepository {
    private func filterStoreTable(storeTable: StoreTable, with categoryFilter: CategoryFilterType) -> Bool {
        switch categoryFilter {
        case .all:
            return true
        case .makgulli:
            return storeTable.categoryType == .makgulli
        case .pajeon:
            return storeTable.categoryType == .pajeon
        case .bossam:
            return storeTable.categoryType == .bossam
        }
    }
}

extension StoreVO {
    func makeStoreTable() -> StoreTable {
        let episodeList = List<EpisodeTable>()
        
        let episodeTableArray = self.episode.map { episodeVO in
            let episodeTable = EpisodeTable()
            episodeTable.comment = episodeVO.comment
            episodeTable.date = episodeVO.date
            episodeTable.alcohol = episodeVO.alcohol
            episodeTable.imageURL = episodeVO.imageURL
            episodeTable.drink = episodeVO.drink
            episodeTable.drinkQuantity = episodeVO.drinkQuantity
            return episodeTable
        }
        
        episodeList.append(objectsIn: episodeTableArray)
        
        let storeTable = StoreTable(id: self.id,
                                    placeName: self.placeName,
                                    distance: self.distance,
                                    placeURL: self.placeURL,
                                    categoryName: self.categoryName,
                                    addressName: self.addressName,
                                    roadAddressName: self.roadAddressName,
                                    phone: self.phone,
                                    x: self.x,
                                    y: self.y,
                                    categoryType: self.categoryType,
                                    rate: self.rate,
                                    bookmark: self.bookmark,
                                    episode: episodeList,
                                    bookmarkDate: self.bookmarkDate)
        return storeTable
    }
}

extension EpisodeVO {
    func makeEpisodeTable() -> EpisodeTable {
        let episodeTable = EpisodeTable(date: self.date,
                                        comment: self.comment,
                                        imageURL: self.imageURL,
                                        alcohol: self.alcohol,
                                        drink: self.drink,
                                        drinkQuantity: self.drinkQuantity)
        return episodeTable
    }
}
protocol RealmRepository {
    func fetchBookmarkStore(sortAscending: Bool, categoryFilter: CategoryFilterType) -> Single<[StoreVO]>
    func fetchBookmarkStoreSortedByRating(sortAscending: Bool, categoryFilter: CategoryFilterType) -> Single<[StoreVO]>
    func fetchStoreSortedByRating(sortAscending: Bool, categoryFilter: CategoryFilterType) -> Single<[StoreVO]>
    func fetchStoreSortedByEpisodeCount(sortAscending: Bool, categoryFilter: CategoryFilterType) -> Single<[StoreVO]>
    func fetchBookmarkStoreSortedByEpisodeCount(sortAscending: Bool, categoryFilter: CategoryFilterType) -> Single<[StoreVO]>
    func fetchStoreSortedByName(sortAscending: Bool, categoryFilter: CategoryFilterType) -> Single<[StoreVO]>
    func createStoreTable(_ store: StoreTable) -> Completable
    func createStore(_ store: StoreVO) -> Completable
    func updateStore(_ store: StoreVO) -> Completable
    func updateEpisode(id: String, episode: EpisodeTable) -> Completable
    func updateStoreEpisode(store: StoreVO) -> StoreVO?
    func updateStoreCellObservable(index: Int, storeList: [StoreVO]) -> Single<StoreVO>
    func updateStoreCell(store: StoreVO) -> StoreVO?
    func deleteStore(_ store: StoreVO) -> Completable
    func deleteEpisode(id: String, episodeId: String) -> Completable
    func checkContainsStore(id: String) -> Bool
    func shouldUpdateStore(_ store: StoreVO) -> Bool
}