package com.p3.bewoplaner.data.repository;

import com.p3.bewoplaner.data.repository.mapper.Mapper;
import com.p3.bewoplaner.data.repository.models.MessageRealm;
import com.p3.bewoplaner.data.repository.specifications.MessageByIdSpecification;
import com.p3.bewoplaner.data.repository.specifications.RealmSpecification;
import com.p3.bewoplaner.data.repository.specifications.Specification;
import com.p3.bewoplaner.model.Message;

import java.util.List;
import java.util.concurrent.Callable;

import io.realm.Sort;
import rx.Completable;
import rx.Observable;
import rx.functions.Func1;

/**
 * Created by Rares Teodorescu on 22/02/2017.
 */
public class MessageRealmRepository implements Repository<Message> {

    private final RealmProvider mRealmProvider;
    private final Mapper<Message, MessageRealm> mToRealmMapper;
    private final Mapper<MessageRealm, Message> mToMessageMapper;

    public MessageRealmRepository(RealmProvider realmProvider,
                                  Mapper<Message, MessageRealm> toRealmMapper,
                                  Mapper<MessageRealm, Message> toMessageMapper) {
        mRealmProvider = realmProvider;
        mToRealmMapper = toRealmMapper;
        mToMessageMapper = toMessageMapper;
    }

    @Override
    public Completable add(Iterable<Message> items) {
        final RealmWrapper realmWrapper = new RealmWrapper();
        return Observable.from(items)
                .doOnSubscribe(() -> {
                    realmWrapper.realm = mRealmProvider.provide();
                    realmWrapper.realm.beginTransaction();
                })
                .map(mToRealmMapper::from)
                .doOnNext(messageRealm -> realmWrapper.realm.insert(messageRealm))
                .doOnError(throwable -> {
                    realmWrapper.realm.cancelTransaction();
                    realmWrapper.realm.close();
                })
                .doOnCompleted(() -> {
                    realmWrapper.realm.commitTransaction();
                    realmWrapper.realm.close();
                })
                .toCompletable();
    }

    @Override
    public Completable update(Message item, Specification specification) {
        RealmWrapper realmWrapper = new RealmWrapper();
        MessageByIdSpecification realmSpecification = (MessageByIdSpecification) specification;
        return Observable.just(item)
                .doOnSubscribe(() -> {
                    realmWrapper.realm = mRealmProvider.provide();
                    realmWrapper.realm.beginTransaction();
                })
                .map(mToRealmMapper::from)
                .doOnNext(newMessage -> {
                    realmSpecification.setMessageId(newMessage.getId());
                    MessageRealm messageToUpdate =
                            realmSpecification.toRealmQuery(realmWrapper.realm).findFirst();
                    if (messageToUpdate != null) {
                        newMessage.setUniqueId(messageToUpdate.getUniqueId());
                    }
                    realmWrapper.realm.copyToRealmOrUpdate(newMessage);
                })
                .doOnError(throwable -> {
                    realmWrapper.realm.cancelTransaction();
                    realmWrapper.realm.close();
                })
                .doOnCompleted(() -> {
                    realmWrapper.realm.commitTransaction();
                    realmWrapper.realm.close();
                })
                .toCompletable();
    }

    @Override
    public Completable update(Iterable<Message> items, Specification specification) {
        final RealmWrapper realmWrapper = new RealmWrapper();
        MessageByIdSpecification realmSpecification = (MessageByIdSpecification) specification;
        return Observable.from(items)
                .doOnSubscribe(() -> {
                    realmWrapper.realm = mRealmProvider.provide();
                    realmWrapper.realm.beginTransaction();
                })
                .map(mToRealmMapper::from)
                .doOnNext(newMessage -> {
                    realmSpecification.setMessageId(newMessage.getId());
                    MessageRealm messageToUpdate =
                            realmSpecification.toRealmQuery(realmWrapper.realm).findFirst();
                    if (messageToUpdate != null) {
                        newMessage.setUniqueId(messageToUpdate.getUniqueId());
                        newMessage.setFileDownloaded(messageToUpdate.isFileDownloaded());
                    }
                    realmWrapper.realm.copyToRealmOrUpdate(newMessage);
                })
                .doOnError(throwable -> {
                    realmWrapper.realm.cancelTransaction();
                    realmWrapper.realm.close();
                })
                .doOnCompleted(() -> {
                    realmWrapper.realm.commitTransaction();
                    realmWrapper.realm.close();
                })
                .toCompletable();
    }

    public Observable<List<Message>> updateMessages(Iterable<Message> items, Specification specification) {
        final RealmWrapper realmWrapper = new RealmWrapper();
        MessageByIdSpecification realmSpecification = (MessageByIdSpecification) specification;
        return Observable.from(items)
                .flatMapIterable(new Func1<Message, Iterable<Message>>() {
                    @Override
                    public Iterable<Message> call(Message message) {
                        return items;
                    }
                })
                .map(mToRealmMapper::from)
                .doOnNext(newMessage -> {
                    realmSpecification.setMessageId(newMessage.getId());
                    MessageRealm messageToUpdate =
                            realmSpecification.toRealmQuery(realmWrapper.realm).findFirst();
                    if (messageToUpdate != null) {
                        newMessage.setUniqueId(messageToUpdate.getUniqueId());
                        newMessage.setFileDownloaded(messageToUpdate.isFileDownloaded());
                    }
                    realmWrapper.realm.copyToRealmOrUpdate(newMessage);
                })
                .flatMap(new Func1<MessageRealm, Observable<Message>>() {
                    @Override
                    public Observable<Message> call(MessageRealm messageRealm) {
                        return Observable.just(mToMessageMapper.from(messageRealm));
                    }
                })
                .toList()
                .doOnSubscribe(() -> {
                    realmWrapper.realm = mRealmProvider.provide();
                    realmWrapper.realm.beginTransaction();
                })
                .doOnError(throwable -> {
                    realmWrapper.realm.cancelTransaction();
                    realmWrapper.realm.close();
                })
                .doOnCompleted(() -> {
                    realmWrapper.realm.commitTransaction();
                    realmWrapper.realm.close();
                });
    }

    @Override
    public Completable remove(Specification specification) {
        RealmWrapper realmWrapper = new RealmWrapper();
        MessageByIdSpecification realmSpecification = (MessageByIdSpecification) specification;
        return Observable.fromCallable(() -> realmSpecification.toRealmQuery(realmWrapper.realm).findFirst())
                .doOnSubscribe(() -> {
                    realmWrapper.realm = mRealmProvider.provide();
                    realmWrapper.realm.beginTransaction();
                })
                .doOnNext(messageRealm -> messageRealm.deleteFromRealm())
                .doOnError(throwable -> {
                    realmWrapper.realm.cancelTransaction();
                    realmWrapper.realm.close();
                })
                .doOnCompleted(() -> {
                    realmWrapper.realm.commitTransaction();
                    realmWrapper.realm.close();
                })
                .toCompletable();
    }

    @Override
    public Completable removeAll() {
        RealmWrapper realmWrapper = new RealmWrapper();
        return Observable.fromCallable(new Callable<Void>() {
            @Override
            public Void call() throws Exception {
                realmWrapper.realm.delete(MessageRealm.class);
                return null;
            }
        })
                .doOnSubscribe(() -> {
                    realmWrapper.realm = mRealmProvider.provide();
                    realmWrapper.realm.beginTransaction();
                })
                .doOnError(throwable -> {
                    realmWrapper.realm.cancelTransaction();
                    realmWrapper.realm.close();
                })
                .doOnCompleted(() -> {
                    realmWrapper.realm.commitTransaction();
                    realmWrapper.realm.close();
                })
                .toCompletable();
    }

    @Override
    public Observable<Message> queryForOneById(String id, Specification specification) {
        RealmWrapper realmWrapper = new RealmWrapper();
        MessageByIdSpecification realmSpecification = (MessageByIdSpecification) specification;
        ((MessageByIdSpecification) specification).setMessageId(id);
        return Observable.fromCallable(() -> realmSpecification.toRealmQuery(realmWrapper.realm).findFirst())
                .map(mToMessageMapper::from)
                .doOnSubscribe(() -> {
                    realmWrapper.realm = mRealmProvider.provide();
                    realmWrapper.realm.beginTransaction();
                })
                .doOnError(throwable -> {
                    realmWrapper.realm.cancelTransaction();
                    realmWrapper.realm.close();
                })
                .doOnCompleted(() -> {
                    realmWrapper.realm.commitTransaction();
                    realmWrapper.realm.close();
                });
    }

    @Override
    public Observable<List<Message>> queryForAll(Specification specification) {
        RealmWrapper realmWrapper = new RealmWrapper();
        RealmSpecification<MessageRealm> realmSpecification = (RealmSpecification) specification;
        return Observable.fromCallable(() -> realmSpecification.toRealmQuery(realmWrapper.realm)
                .findAllSorted(MessageRealm.CREATED_AT, Sort.DESCENDING))
                .flatMapIterable(talksRealms -> talksRealms)
                .flatMap(new Func1<MessageRealm, Observable<Message>>() {
                    @Override
                    public Observable<Message> call(MessageRealm userRealm) {
                        return Observable.just(mToMessageMapper.from(userRealm));
                    }
                })
                .toList()
                .doOnSubscribe(() -> {
                    realmWrapper.realm = mRealmProvider.provide();
                    realmWrapper.realm.beginTransaction();
                })
                .doOnError(throwable -> {
                    realmWrapper.realm.cancelTransaction();
                    realmWrapper.realm.close();
                })
                .doOnCompleted(() -> {
                    realmWrapper.realm.commitTransaction();
                    realmWrapper.realm.close();
                });
    }

}
