import net.lingala.zip4j.core.ZipFile;
import net.lingala.zip4j.exception.ZipException;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;

import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.Table;
import java.io.File;
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;

/**
 * Created by shumailovr on 09.04.15.
 */

@Entity
@Table(name = "gutenberg_books_catalogue")
public class GutenbergBookModel {

    private GutenbergBookModelId gutenbergBookModelId;

    public GutenbergBookModel() {
    }

    public GutenbergBookModel(GutenbergBookModelId gutenbergBookModelId) {
        this.gutenbergBookModelId = gutenbergBookModelId;
    }

    @EmbeddedId
    public GutenbergBookModelId getGutenbergBookModelId() {
        return gutenbergBookModelId;
    }

    public void setGutenbergBookModelId(GutenbergBookModelId gutenbergBookModelId) {
        this.gutenbergBookModelId = gutenbergBookModelId;
    }

    /**
     * Processes file, and if is .zip file of saved gutenberg book, processes it to get title and author for catalogue creation
     * @param file
     * @param persistenceManager
     * @return
     */
    public static GutenbergBookModel processTitleAndAuthor(Path file, PersistenceManager persistenceManager) {
        if (FilenameUtils.getExtension(file.toString()).equals("zip")) {
            //Open the file, read in the book
            //Make new directory where to extract the contents of the book zip file
            File destDir = new File(FilenameUtils.removeExtension(file.toString()));
            destDir.mkdir();
            File bookDir = destDir;
            ZipFile zf = null;
            try {
                //Extract contents of the zip file to dest directory
                System.out.println("extracting");
                zf = new ZipFile(file.toString());
                zf.extractAll(bookDir.getPath());

                //If zip file contained directory that contained book with other files then change
                //place where we look for the book to this directory
                if (bookDir.listFiles().length == 1 & bookDir.listFiles()[0].isDirectory()) {
                    bookDir = bookDir.listFiles()[0];
                }

                //Search for the book, since the gutenberg books don't have unified format,
                //in this work I will assume that first encountered .html or .htm file is the book
                for (File extractedFile : bookDir.listFiles()) {
                    if ((FilenameUtils.getExtension(extractedFile.getName()).equals("html") ||
                            FilenameUtils.getExtension(extractedFile.getName()).equals("htm")) &&
                            !FilenameUtils.removeExtension(extractedFile.getName()).equals("title")) {
                        org.jsoup.nodes.Document bookDoc = ReadBookFile.readBookIn(extractedFile);
                        String bookTitle = ReadBookFile.readBookTitle(bookDoc);
                        String bookAuthor = ReadBookFile.readBookAuthor(bookDoc);
                        //If we already have such book in catalogue, skip
                        if (persistenceManager.getGutenbergBook(bookTitle, bookAuthor) != null) {
                            continue;
                        }
                        //Lets put it this way, if I can't find book author and title, then I assume this is corrupted
                        //file and I have book author and title as composite primary key anyway
                        if (!bookTitle.isEmpty() && !bookAuthor.isEmpty()) {
                            //Read the book
                            String bookContent = ReadBookFile.readBookContent(bookDoc);
                            //If book is not empty
                            if (!bookContent.isEmpty()) {
                                //save the book in the catalogue
                                GutenbergBookModel gutenbergBookModel = new GutenbergBookModel(new GutenbergBookModelId(bookTitle, bookAuthor));
                                persistenceManager.saveGutenbergBook(gutenbergBookModel);
                                return gutenbergBookModel;
                            }
                            break;
                        }
                    }
                }

                //Delete dir we created after we analyzed all we wanted, otherwise my computer will run out of space
                try {
                    FileUtils.deleteDirectory(destDir);
                    System.out.println("EXTRACTED DIRECTORY DELETED");
                } catch (IOException e) {
                    e.printStackTrace();
                }

            } catch (ZipException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * This class traverses all directories starting with main directory of gutenberg books and only takes title and author
     * from them in order to create a catalogue
     */
    public static class TraverseBooksAndCreateCatalogue extends SimpleFileVisitor<Path> {

        private PersistenceManager persistenceManager;

        @Override
        public FileVisitResult visitFile(Path file,
                                         BasicFileAttributes attr) {
            //Copy here file management code from main class, maybe create separate method for it
            System.out.println("File: " + file);
            GutenbergBookModel gutenbergBookModel = processTitleAndAuthor(file, persistenceManager);
            if (gutenbergBookModel != null) {
                System.out.println("***** INSERTED *****");
                System.out.println("TITLE: " + gutenbergBookModel.getGutenbergBookModelId().getTitle());
                System.out.println("AUTHOR: " + gutenbergBookModel.getGutenbergBookModelId().getAuthor());
                System.out.println("***** INSERTED *****");
            }
            return FileVisitResult.CONTINUE;
        }

        // Print each directory visited.
        @Override
        public FileVisitResult postVisitDirectory(Path dir,
                                                  IOException exc) {
            System.out.format("Directory: %s%n", dir);
            return FileVisitResult.CONTINUE;
        }

        // If there is some error accessing the file, let the user know.
        // If you don't override this method and an error occurs, an IOException is thrown.
        @Override
        public FileVisitResult visitFileFailed(Path file,
                                               IOException exc) {
            System.err.println(exc);
            return FileVisitResult.CONTINUE;
        }

        public PersistenceManager getPersistenceManager() {
            return persistenceManager;
        }

        public void setPersistenceManager(PersistenceManager persistenceManager) {
            this.persistenceManager = persistenceManager;
        }
    }

    /**
     * Run this method to trigger book catalogue creation
     */
    public static void startTraverseAndCatalogueCreation() {
        //Mark path manually
        Path startingDir = Paths.get("gutenbergHtmlBooks/www.gutenberg.lib.md.us");
        TraverseBooksAndCreateCatalogue rb = new TraverseBooksAndCreateCatalogue();
        rb.setPersistenceManager(new PersistenceManager());
        try {
            Files.walkFileTree(startingDir, rb);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}
