package com.todoroo.astrid.service import android.content.Context import com.todoroo.andlib.utility.DateUtilities import com.todoroo.astrid.api.CaldavFilter import com.todoroo.astrid.api.Filter import com.todoroo.astrid.api.GtasksFilter import com.todoroo.astrid.data.Task import dagger.hilt.android.qualifiers.ApplicationContext import org.tasks.BuildConfig import org.tasks.LocalBroadcastManager import org.tasks.caldav.VtodoCache import org.tasks.data.* import org.tasks.db.DbUtils.dbchunk import org.tasks.preferences.Preferences import org.tasks.sync.SyncAdapters import javax.inject.Inject class TaskMover @Inject constructor( @param:ApplicationContext private val context: Context, private val taskDao: TaskDao, private val caldavDao: CaldavDao, private val googleTaskDao: GoogleTaskDao, private val googleTaskListDao: GoogleTaskListDao, private val preferences: Preferences, private val localBroadcastManager: LocalBroadcastManager, private val syncAdapters: SyncAdapters, private val vtodoCache: VtodoCache, ) { suspend fun getSingleFilter(tasks: List): Filter? { val caldavCalendars = caldavDao.getCalendars(tasks) val googleTaskLists = googleTaskDao.getLists(tasks) if (caldavCalendars.isEmpty()) { if (googleTaskLists.size == 1) { return GtasksFilter(googleTaskListDao.getByRemoteId(googleTaskLists[0])) } } else if (googleTaskLists.isEmpty()) { if (caldavCalendars.size == 1) { return CaldavFilter(caldavDao.getCalendar(caldavCalendars[0])) } } return null } suspend fun move(ids: List, selectedList: Filter) { val tasks = ids .dbchunk() .flatMap { taskDao.getChildren(it) } .let { taskDao.fetch(ids.minus(it.toSet())) } .filterNot { it.readOnly } val taskIds = tasks.map { it.id } taskDao.setParent(0, ids.intersect(taskIds.toSet()).toList()) tasks.forEach { performMove(it, selectedList) } if (selectedList is CaldavFilter) { caldavDao.updateParents(selectedList.uuid) } taskIds.dbchunk().forEach { taskDao.touch(it) } localBroadcastManager.broadcastRefresh() syncAdapters.sync() } suspend fun migrateLocalTasks() { val list = caldavDao.getLocalList(context) move(taskDao.getLocalTasks(), CaldavFilter(list)) } private suspend fun performMove(task: Task, selectedList: Filter) { googleTaskDao.getByTaskId(task.id)?.let { moveGoogleTask(task, it, selectedList) return } caldavDao.getTask(task.id)?.let { moveCaldavTask(task, it, selectedList) return } moveLocalTask(task, selectedList) } private suspend fun moveGoogleTask(task: Task, googleTask: CaldavTask, selected: Filter) { if (selected is GtasksFilter && googleTask.calendar == selected.remoteId) { return } val id = task.id val children = taskDao.getChildren(id) caldavDao.markDeleted(children + id, DateUtilities.now()) when(selected) { is GtasksFilter -> { val listId = selected.remoteId googleTaskDao.insertAndShift( task = task, caldavTask = CaldavTask(id, listId, remoteId = null), top = preferences.addTasksToTop() ) children.takeIf { it.isNotEmpty() } ?.map { CaldavTask(task = it, calendar = listId, remoteId = null) } ?.let { googleTaskDao.insert(it) } } is CaldavFilter -> { val listId = selected.uuid val newParent = CaldavTask(id, listId) caldavDao.insert(task, newParent, preferences.addTasksToTop()) children.map { val newChild = CaldavTask(it, listId) newChild.remoteParent = newParent.remoteId newChild }.let { caldavDao.insert(it) } } else -> require(!BuildConfig.DEBUG) } } private suspend fun moveCaldavTask(task: Task, caldavTask: CaldavTask, selected: Filter) { if (selected is CaldavFilter && caldavTask.calendar == selected.uuid) { return } val id = task.id val childIds = taskDao.getChildren(id) val toDelete = arrayListOf(id) var children: List = emptyList() if (childIds.isNotEmpty()) { children = caldavDao.getTasks(childIds) toDelete.addAll(childIds) } caldavDao.markDeleted(toDelete, DateUtilities.now()) when (selected) { is CaldavFilter -> { val from = caldavDao.getCalendar(caldavTask.calendar!!) val id1 = caldavTask.task val listId = selected.uuid val newParent = CaldavTask(id1, listId, caldavTask.remoteId, caldavTask.`object`) vtodoCache.move(from!!, selected.calendar, caldavTask) caldavDao.insert(task, newParent, preferences.addTasksToTop()) children.takeIf { it.isNotEmpty() } ?.map { val newChild = CaldavTask(it.task, listId, it.remoteId, it.`object`) vtodoCache.move(from, selected.calendar, it) newChild.remoteParent = it.remoteParent newChild } ?.let { caldavDao.insert(it) } } is GtasksFilter -> moveToGoogleTasks(id, childIds, selected) else -> require(!BuildConfig.DEBUG) } } private suspend fun moveLocalTask(task: Task, selected: Filter) { when (selected) { is GtasksFilter -> moveToGoogleTasks(task.id, taskDao.getChildren(task.id), selected) is CaldavFilter -> { val id = task.id val listId = selected.uuid val tasks: MutableMap = HashMap() val root = CaldavTask(id, listId) val children = taskDao.getChildren(id).mapNotNull { taskDao.fetch(it) } for (child in children) { val newTask = CaldavTask(child.id, listId) val parent = child.parent newTask.remoteParent = (if (parent == id) root else tasks[parent])!!.remoteId tasks[child.id] = newTask } caldavDao.insert(task, root, preferences.addTasksToTop()) caldavDao.insert(tasks.values) } else -> require(!BuildConfig.DEBUG) } } private suspend fun moveToGoogleTasks(id: Long, children: List, filter: GtasksFilter) { val task = taskDao.fetch(id) ?: return taskDao.setParent(id, children) val listId = filter.remoteId googleTaskDao.insertAndShift( task, CaldavTask(id, listId, remoteId = null), preferences.addTasksToTop() ) children.takeIf { it.isNotEmpty() } ?.map { CaldavTask(it, listId, remoteId = null) } ?.let { googleTaskDao.insert(it) } } }