/*
 * Copyright 2006-2007 Queplix Corp.
 *
 * Licensed under the Queplix Public License, Version 1.1.1 (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.queplix.com/solutions/commercial-open-source/queplix-public-license/
 *
 * 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 com.queplix.core.modules.eqlext.utils.images;

import com.queplix.core.error.GenericSystemException;
import com.queplix.core.integrator.security.LogonSession;
import com.queplix.core.modules.config.ejb.EntityViewConfigManagerLocal;
import com.queplix.core.modules.config.ejb.EntityViewConfigManagerLocalHome;
import com.queplix.core.modules.eql.error.EQLException;
import com.queplix.core.modules.eqlext.error.FileManagerException;
import com.queplix.core.modules.eqlext.utils.FileManager;
import com.queplix.core.modules.jeo.JEObjectHandler;
import com.queplix.core.modules.jeo.ejb.JEOManagerLocal;
import com.queplix.core.modules.jeo.ejb.JEOManagerLocalHome;
import com.queplix.core.modules.jeo.gen.AttachmentObject;
import com.queplix.core.modules.jeo.gen.AttachmentObjectHandler;
import com.queplix.core.modules.jeo.gen.ImageObject;
import com.queplix.core.modules.jeo.gen.ImageObjectHandler;
import com.queplix.core.modules.jeo.gen.ImageUsageObject;
import com.queplix.core.modules.jeo.gen.ImageUsageObjectHandler;
import com.queplix.core.modules.jeo.gen.UploadFolderObject;
import com.queplix.core.modules.jeo.gen.UploadFolderObjectHandler;
import com.queplix.core.utils.DateHelper;
import com.queplix.core.utils.FileHelper;
import com.queplix.core.utils.JNDINames;
import com.queplix.core.utils.StringHelper;
import com.queplix.core.utils.cache.CacheObjectManager;
import com.queplix.core.utils.log.AbstractLogger;
import org.apache.commons.fileupload.FileItem;

import java.io.File;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;


/**
 * Attachment's file and directory helper class.
 *
 * @author [ALB] Andrey Baranov
 * @version $Revision: 1.1.1.1 $ $Date: 2005/09/12 15:30:43 $
 * @see com.queplix.core.modules.eqlext.utils.FileManager
 * @see com.queplix.core.modules.eqlext.www.FileUploadServlet
 */

public final class FileUploadManager
        extends AbstractLogger {

    // ========================================================== Variables

    // User logon session.
    private LogonSession ls;


    // Cache local intterfaces of EJB.
    private CacheObjectManager com = new CacheObjectManager();

    // ========================================================== Constrcutor

    public FileUploadManager(LogonSession ls) {
        this.ls = ls;
    }

    // ========================================================== Public methods

    /**
     * Loads the file by ID (as the <code>FileManager.	FileInfo</code> object).
     *
     * @param fileID file ID
     * @return FileManager.FileInfo object
     * @throws FileManagerException
     */
    public FileManager.FileInfo loadFile(long fileID)
            throws FileManagerException {

        INFO("Loading file (ID = " + fileID + ")...");

        try {
            JEObjectHandler hnd = ImageObjectHandler.selectByPkey(
                    getJEOManager(), ls, fileID);
            if(hnd == null) {
                throw new IllegalStateException(
                        "Can't load file info record with ID = " + fileID);
            }
            ImageObject obj = (ImageObject) hnd.getJEObject();

            return FileManager.loadFile(FileManager.getLocalName(
                    obj.getFileName(), obj.getFolder()));

        } catch (EQLException ex) {
            ERROR(ex);
            throw new GenericSystemException(ex);
        }
    }


    /**
     * Loads the file by ID.
     *
     * @param fileID file ID
     * @return byte array
     * @throws FileManagerException
     */
    public byte[] loadFileAsByteArray(long fileID)
            throws FileManagerException {

        INFO("Loading file (ID = " + fileID + ")...");

        try {
            JEObjectHandler hnd = AttachmentObjectHandler.selectByPkey(
                    getJEOManager(), ls, fileID);
            if(hnd == null) {
                throw new IllegalStateException(
                        "Can't load file info record with ID = " + fileID);
            }
            AttachmentObject obj = (AttachmentObject) hnd.getJEObject();

            return obj
                    .getData();   //FileManager.loadFile( FileManager.getLocalName( obj.getFileName(), obj.getFolder() ) );

        } catch (EQLException ex) {
            ERROR(ex);
            throw new GenericSystemException(ex);
        }
    }

    /**
     * Saves the file and creates the related record.
     *
     * @param fileInfo   FileInfo object
     * @param subFolder  subfolder to store (optional)
     * @param entityName entity name (optional)
     * @param recordKey  record key for wich we store file (optional)
     * @return File object
     * @throws FileManagerException
     */
    public File saveFile(FileManager.FileInfo fileInfo,
                         String subFolder,
                         String entityName,
                         Long recordKey)
            throws FileManagerException {

        File file = null;
        try {
            // Do saving.
            file = FileManager.saveFile(fileInfo, subFolder);

            // Add the related database record.
            addFileRecord(file, entityName, recordKey);

        } catch (EQLException ex) {
            ERROR(ex);
            throw new GenericSystemException(ex);
        }

        return file;
    }


    /*
     * No javadoc.
     * @see saveFile( FileManager.FileInfo, .. )
     */
    public File saveFile(FileItem fileItem,
                         String subFolder,
                         String entityName,
                         Long recordKey)
            throws FileManagerException {

        FileManager.FileInfo fileInfo = new FileManager.FileInfo(
                fileItem.getName(), fileItem.get());
        return saveFile(fileInfo, subFolder, entityName, recordKey);
    }


    /**
     * Saves the file
     *
     * @param fileInfo  FileInfo object
     * @param subFolder subfolder to store (optional)
     * @return File object
     * @throws FileManagerException
     */
    public File saveFile(FileManager.FileInfo fileInfo,
                         String subFolder)
            throws FileManagerException {

        File file = null;
        // Do saving.
        file = FileManager.saveFile(fileInfo, subFolder);

        return file;
    }


    /*
     * No javadoc.
     * @see saveFile( FileManager.FileInfo, .. )
     */
    public File saveFile(FileItem fileItem,
                         String subFolder)
            throws FileManagerException {

        FileManager.FileInfo fileInfo = new FileManager.FileInfo(
                fileItem.getName(), fileItem.get());
        return saveFile(fileInfo, subFolder);
    }

    /**
     * Replaces the existing file and updates the related record.
     *
     * @param fileID   file ID
     * @param fileItem FileItem object
     * @return File object of new file
     * @throws FileManagerException
     */
    public File replaceFile(long fileID, FileItem fileItem)
            throws FileManagerException {

        INFO("Replacing the file (ID = " + fileID + ")...");

        File file = null;
        try {

            // Get the related database record.
            JEObjectHandler hnd = ImageObjectHandler.selectByPkey(
                    getJEOManager(), ls, fileID);
            if(hnd == null) {
                throw new IllegalStateException(
                        "Can't load file info record with ID = " + fileID);
            }
            ImageObject obj = (ImageObject) hnd.getJEObject();

            // Construct FileInfo.
            FileManager.FileInfo fileInfo = new FileManager.FileInfo(
                    fileItem.getName(), fileItem.get());

            // Do replacing.
            file = FileManager.replaceFile(FileManager.getLocalName(
                    obj.getFileName(), obj.getFolder()),
                    fileInfo);

            // Update the related database record.
            updateFileRecord((ImageObjectHandler) hnd, file);

        } catch (EQLException ex) {
            ERROR(ex);
            throw new GenericSystemException(ex);
        }

        return file;
    }


    /**
     * Deletes the existing file and updates the related record.
     *
     * @param fileID file ID
     * @throws FileManagerException
     */
    public void deleteFile(long fileID)
            throws FileManagerException {

        INFO("Deleting the file (ID = " + fileID + ")...");

        try {

            // Get the related database record.
            JEObjectHandler hnd = ImageObjectHandler.selectByPkey(
                    getJEOManager(), ls, fileID);
            if(hnd == null) {
                throw new IllegalStateException(
                        "Can't load file info record with ID = " + fileID);
            }
            ImageObject obj = (ImageObject) hnd.getJEObject();

            // Do deleting.
            FileManager.deleteFile(FileManager.getLocalName(obj.getFileName(),
                    obj.getFolder()));

            // Delete the related database record.
            hnd.remove();
            hnd.commit();

        } catch (EQLException ex) {
            ERROR(ex);
            throw new GenericSystemException(ex);
        }

    }


    /**
     * Synchronizes the directory tree with related database tables.
     *
     * @throws FileManagerException
     */
    public synchronized void syncDirectories()
            throws FileManagerException {

        long time = System.currentTimeMillis();
        INFO("Syncing the directories tree with database...");

        JEOManagerLocal jeoManager = getJEOManager();
        int added = 0;
        int removed = 0;

        try {
            // 1. Get the list of the folder objects in the database.
            List hnds = UploadFolderObjectHandler.getAllFolders(jeoManager, ls);
            int dbSize = (hnds == null) ? 0:hnds.size();

            DEBUG("Found " + dbSize + " database directory objects.");

            List dbDirList = new ArrayList(dbSize);
            for(int i = 0; i < dbSize; i++) {
                UploadFolderObjectHandler hnd = (UploadFolderObjectHandler) hnds
                        .get(i);
                UploadFolderObject obj = (UploadFolderObject) hnd.getJEObject();
                String dirName = obj.getFolder();
                if(StringHelper.isEmpty(dirName)) {
                    throw new NullPointerException(
                            "Name is empty for the directory object with ID = "
                                    +
                                    obj.getFolder_id().longValue());
                }
                dbDirList.add(FileManager.getFullFile(dirName));
            }

            // 2. Get all existing folders.
            List fsDirList = FileHelper.getDirectoriesList(
                    FileManager.getFSDirectory());
            int fsSize = (fsDirList == null) ? 0:fsDirList.size();
            DEBUG("Found " + fsSize + " existing directories.");

            // 3. Add missing records to the database.
            for(int i = 0; i < fsSize; i++) {
                File dir = (File) fsDirList.get(i);
                if(!dbDirList.contains(dir)) {
                    JEObjectHandler hnd = jeoManager.create(ls,
                            UploadFolderObjectHandler.class);
                    UploadFolderObject obj = (UploadFolderObject) hnd
                            .getJEObject();
                    obj.setFolder(FileManager.getLocalName(dir));
                    hnd.commit();
                    added++;

                    if(getLogger().isDebugEnabled()) {
                        String path = dir.getAbsolutePath();
                        DEBUG("Record added for the '" + path + "' directory.");
                    }
                }
            }

            // 4. Remove extra records from the database.
            for(int i = 0; i < dbSize; i++) {
                File dir = (File) dbDirList.get(i);
                if(!fsDirList.contains(dir)) {
                    UploadFolderObjectHandler hnd
                            = (UploadFolderObjectHandler) hnds.get(i);
                    hnd.remove();
                    hnd.commit();
                    removed++;

                    if(getLogger().isDebugEnabled()) {
                        String path = dir.getAbsolutePath();
                        DEBUG("Record removed for the '" + path
                                + "' directory.");
                    }
                }
            }

        } catch (EQLException ex) {
            ERROR(ex);
            throw new GenericSystemException(ex);
        }

        if(getLogger().isInfoEnabled()) {
            time = System.currentTimeMillis() - time;
            INFO("The directories tree is synced with database. Time(ms) = " +
                    time + ". Records added - " + added + ", removed - " +
                    removed + ".");
        }

    }


    /**
     * Synchronizes the files tree with related database tables.
     *
     * @throws FileManagerException
     */
    public synchronized void syncFiles()
            throws FileManagerException {

        long time = System.currentTimeMillis();
        INFO("Syncing the files tree with database...");

        JEOManagerLocal jeoManager = getJEOManager();
        File fs = FileManager.getFSDirectory();
        int added = 0;
        int removed = 0;

        try {

            // 1. Get the list of the file objects in the database.
            List hnds = ImageObjectHandler.selectAll(jeoManager, ls);
            int dbSize = (hnds == null) ? 0:hnds.size();
            DEBUG("Found " + dbSize + " database file objects.");

            List dbFileList = new ArrayList(dbSize);
            for(int i = 0; i < dbSize; i++) {
                ImageObjectHandler fileHandler = (ImageObjectHandler) hnds.get(
                        i);
                ImageObject fileObject = (ImageObject) fileHandler
                        .getJEObject();
                String dirName = fileObject.getFolder();
                String fileName = fileObject.getFileName();
                if(fileName == null) {
                    throw new NullPointerException("File name is empty.");
                }
                dbFileList.add(FileManager.getFullFile(dirName, fileName));
            }

            // 2. Get all existing files.
            List fsFileList = FileHelper.getFilesList(fs);
            int fsSize = (fsFileList == null) ? 0:fsFileList.size();
            DEBUG("Found " + fsSize + " existing files.");

            // 3. Add missing records to the database.
            for(int i = 0; i < fsSize; i++) {
                File file = (File) fsFileList.get(i);
                if(!dbFileList.contains(file)) {
                    addFileRecord(file, null, null);
                    added++;

                    if(getLogger().isDebugEnabled()) {
                        String path = file.getAbsolutePath();
                        DEBUG("Record added for the '" + path + "' file.");
                    }
                }
            }

            // 4. Remove extra records from the database.
            for(int i = 0; i < dbSize; i++) {
                File file = (File) dbFileList.get(i);
                if(!fsFileList.contains(file)) {
                    ImageObjectHandler hnd = (ImageObjectHandler) hnds.get(i);
                    hnd.remove();
                    hnd.commit();
                    removed++;

                    if(getLogger().isDebugEnabled()) {
                        String path = file.getAbsolutePath();
                        DEBUG("Record removed for the '" + path + "' file.");
                    }
                }
            }

        } catch (EQLException ex) {
            ERROR(ex);
            throw new GenericSystemException(ex);
        }

        if(getLogger().isInfoEnabled()) {
            time = System.currentTimeMillis() - time;
            INFO("The files tree is synced with database. Time(ms) = " +
                    time + ". Records added - " + added + ", removed - " +
                    removed + ".");
        }
    }


    /**
     * Gets the LOCAL name for the given file.
     *
     * @param file given File object
     * @return LOCAL name
     * @throws FileManagerException
     * @see FileManager#getLocalName
     */
    public static String getLocalName(File file)
            throws FileManagerException {

        // Get file paths.
        String filePath = FileManager.getLocalName(file);

        // File path _MUST_ be following: <folder>/<file name>
        // If no folder - add '.'!
        if(filePath.indexOf(FileManager.NAME_SEPARATOR) < 0) {
            filePath = "." + FileManager.NAME_SEPARATOR + filePath;
        }

        return filePath;
    }

    // ========================================================= Protected methods

    // Adds record about file to the database

    protected void addFileRecord(File file,
                                 String entityName,
                                 Long recordKey)
            throws EQLException {

        JEOManagerLocal jeoManager = getJEOManager();

        Date now = DateHelper.getNowDate();
        String fileName = file.getName();
        Long fileSize = new Long(file.length());
        String localFolderName = FileManager.getLocalName(file.getParentFile());

        // Add the master record.
        JEObjectHandler imageHnd = jeoManager.create(ls,
                ImageObjectHandler.class);
        ImageObject imageObj = (ImageObject) imageHnd.getJEObject();
        imageObj.setFilename(fileName);
        imageObj.setFolder(localFolderName);
        imageObj.setFile_size(fileSize);
        imageObj.setCreator_id(new Long(ls.getUser().getUserID()));
        imageObj.setModifier_id(new Long(ls.getUser().getUserID()));
        imageObj.setCreated(now);
        imageObj.setModified(now);
        imageHnd.commit();

        Long fileID = imageObj.getImage_id();

        // Add the record about file usage.
        if(entityName != null) {

            // Get the database object name.
            EntityViewConfigManagerLocal entityManager =
                    (EntityViewConfigManagerLocal) new CacheObjectManager()
                            .getLocalObject(
                                    JNDINames.EntityViewConfigManager,
                                    EntityViewConfigManagerLocalHome.class);

            String tableName = entityManager.getEntityViewConfig(entityName)
                    .getDbobject();

            // Update image record.
            JEObjectHandler usageHnd = jeoManager.create(ls,
                    ImageUsageObjectHandler.class);
            ImageUsageObject usageObj = (ImageUsageObject) usageHnd
                    .getJEObject();
            usageObj.setImage_id(fileID);
            usageObj.setRecord_key(recordKey);
            usageObj.setTable_name(tableName);
            usageHnd.commit();
        }

    }


    // Update the file record in the database.
    protected void updateFileRecord(ImageObjectHandler hnd,
                                    File file)
            throws EQLException {

        ImageObject obj = (ImageObject) (hnd.getJEObject());
        obj.setModifier_id(new Long(ls.getUser().getUserID()));
        obj.setModified(DateHelper.getNowDate());
        obj.setFile_size(new Long(file.length()));
        hnd.commit();
    }


    // Gets JEO Manager EJB local interface.
    protected JEOManagerLocal getJEOManager() {
        return (JEOManagerLocal) com.
                getLocalObject(JNDINames.JEOManager, JEOManagerLocalHome.class);
    }
}
