// Copyright 2015 Eivind Vegsundvåg
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package ninja.eivind.hotsreplayuploader.repositories;

import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.spring.DaoFactory;
import com.j256.ormlite.stmt.DeleteBuilder;
import com.j256.ormlite.stmt.PreparedQuery;
import com.j256.ormlite.stmt.SelectArg;
import com.j256.ormlite.support.ConnectionSource;
import ninja.eivind.hotsreplayuploader.di.Initializable;
import ninja.eivind.hotsreplayuploader.files.AccountDirectoryWatcher;
import ninja.eivind.hotsreplayuploader.models.ReplayFile;
import ninja.eivind.hotsreplayuploader.models.UploadStatus;

import javax.inject.Inject;
import javax.inject.Singleton;
import java.io.Closeable;
import java.io.IOException;
import java.sql.SQLException;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.stream.Collectors;

/**
 * Implementation of a {@link FileRepository}, which is based on a database backend.<br>
 * Uses ORMLite to abstract database access.
 */
@Singleton
public class OrmLiteFileRepository implements FileRepository, Initializable, Closeable {

    @Inject
    private ConnectionSource connectionSource;
    private Dao<ReplayFile, Long> dao;
    @Inject
    private AccountDirectoryWatcher accountDirectoryWatcher;
    private Dao<UploadStatus, Long> statusDao;

    public OrmLiteFileRepository() {
    }

    public OrmLiteFileRepository(ConnectionSource connectionSource) {
        this.connectionSource = connectionSource;
    }

    /**
     * Initializes this object after all members have been injected. Called automatically by the IoC context.
     */
    @Override
    public void initialize() {
        try {
            dao = DaoFactory.createDao(connectionSource, ReplayFile.class);
            statusDao = DaoFactory.createDao(connectionSource, UploadStatus.class);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void deleteReplay(final ReplayFile replayFile) {
        try {
            dao.delete(replayFile);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void updateReplay(final ReplayFile file) {
        try {
            dao.createOrUpdate(file);
            for (UploadStatus uploadStatus : file.getUploadStatuses()) {
                statusDao.createOrUpdate(uploadStatus);
            }
            dao.refresh(file);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public List<ReplayFile> getAll() {
        //update the DB with any file changes first
        checkForDatabaseIntegrity(accountDirectoryWatcher.getAllFiles()
                .map(ReplayFile::fromDirectory)
                .flatMap(List::stream)
                .collect(Collectors.toList()));

       //get a fully refreshed copy containing all changes from the db
        try {
            return dao.queryForAll();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    private void checkForDatabaseIntegrity(List<ReplayFile> replayFiles) {
        try {
            final List<ReplayFile> fromDb = dao.queryForAll();

            //start a batch task to speed up initial startups
            dao.callBatchTasks(new Callable<Void>() {
                public Void call() throws Exception {
                    //create a db entry for every new physical file
                    replayFiles.stream().filter(r -> !fromDb.contains(r)).forEach(r -> createReplay(r));
                    //remove non-existing files from the db
                    fromDb.stream().filter(r -> !replayFiles.contains(r)).forEach(r -> deleteReplay(r));

                    return null;
                }
            });
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void deleteByFileName(ReplayFile file) {
        final SelectArg selectArg = new SelectArg("fileName", file.getFileName());
        try {
            final DeleteBuilder<ReplayFile, Long> deleteBuilder = dao.deleteBuilder();
            deleteBuilder.where()
                    .eq("fileName", selectArg);
            dao.delete(deleteBuilder.prepare());
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public ReplayFile findById(long id) {
        try {
            return dao.queryForId(id);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    private void createReplay(final ReplayFile replayFile) {
        try {
            dao.create(replayFile);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    private ReplayFile getByFileName(final ReplayFile replayFile) {
        try {
            final SelectArg selectArg = new SelectArg("fileName", replayFile.getFileName());
            final PreparedQuery<ReplayFile> query = dao.queryBuilder()
                    .where().eq("fileName", selectArg)
                    .prepare();

            return dao.query(query).stream()
                    .findAny()
                    .orElse(replayFile);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }

    }

    @Override
    public void close() throws IOException {
        connectionSource.closeQuietly();
    }
}

@@ -3838,20 +3838,17 @@
 chTasks(
-new 
+(
 Callable
@@ -3857,71 +3857,18 @@
 oid%3E
-(
 ) 
-%7B%0A                public Void call() throws Exception %7B%0A    
+() -%3E %7B%0A
     
@@ -3943,20 +3943,16 @@
         
-    
 replayFi
@@ -4005,21 +4005,22 @@
 forEach(
-r -%3E 
+this::
 createRe
@@ -4027,18 +4027,11 @@
 play
-(r));%0A    
+);%0A
     
@@ -4078,28 +4078,24 @@
 from the db%0A
-    
             
@@ -4160,21 +4160,22 @@
 forEach(
-r -%3E 
+this::
 deleteRe
@@ -4182,19 +4182,12 @@
 play
-(r)
 );%0A%0A
-    
     
@@ -4215,26 +4215,8 @@
 ll;%0A
-                %7D%0A
     

flatten anonymous class to lambda