/**
 * This software was developed and / or modified by Raytheon Company,
 * pursuant to Contract DG133W-05-CQ-1067 with the US Government.
 * 
 * U.S. EXPORT CONTROLLED TECHNICAL DATA
 * This software product contains export-restricted data whose
 * export/transfer/disclosure is restricted by U.S. law. Dissemination
 * to non-U.S. persons whether in the United States or abroad requires
 * an export license or other authorization.
 * 
 * Contractor Name:        Raytheon Company
 * Contractor Address:     6825 Pine Street, Suite 340
 *                         Mail Stop B8
 *                         Omaha, NE 68106
 *                         402.291.0100
 * 
 * See the AWIPS II Master Rights File ("Master Rights File.pdf") for
 * further licensing information.
 **/
package com.raytheon.edex.services;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import com.raytheon.edex.utility.ProtectedFiles;
import com.raytheon.uf.common.auth.user.IUser;
import com.raytheon.uf.common.localization.FileUpdatedMessage;
import com.raytheon.uf.common.localization.FileUpdatedMessage.FileChangeType;
import com.raytheon.uf.common.localization.LocalizationContext;
import com.raytheon.uf.common.localization.LocalizationContext.LocalizationLevel;
import com.raytheon.uf.common.localization.PathManagerFactory;
import com.raytheon.uf.common.localization.exception.LocalizationException;
import com.raytheon.uf.common.localization.stream.AbstractLocalizationStreamRequest;
import com.raytheon.uf.common.localization.stream.LocalizationStreamGetRequest;
import com.raytheon.uf.common.localization.stream.LocalizationStreamPutRequest;
import com.raytheon.uf.edex.auth.resp.AuthorizationResponse;
import com.raytheon.uf.edex.core.EDEXUtil;

/**
 * Base handler for localization streaming requests. Delegates work off to a
 * get/put handler
 * 
 * <pre>
 * 
 * SOFTWARE HISTORY
 * Date         Ticket#    Engineer    Description
 * ------------ ---------- ----------- --------------------------
 * Aug 11, 2010            mschenke     Initial creation
 * 
 * </pre>
 * 
 * @author mschenke
 * @version 1.0
 */

public class LocalizationStreamHandler
        extends
        AbstractPrivilegedLocalizationRequestHandler<AbstractLocalizationStreamRequest> {

    private class Pair {
        LocalizationContext context;

        String fileName;

        private Pair(LocalizationContext context, String fileName) {
            this.context = context;
            this.fileName = fileName;
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + getOuterType().hashCode();
            result = prime * result
                    + ((context == null) ? 0 : context.hashCode());
            result = prime * result
                    + ((fileName == null) ? 0 : fileName.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            Pair other = (Pair) obj;
            if (!getOuterType().equals(other.getOuterType()))
                return false;
            if (context == null) {
                if (other.context != null)
                    return false;
            } else if (!context.equals(other.context))
                return false;
            if (fileName == null) {
                if (other.fileName != null)
                    return false;
            } else if (!fileName.equals(other.fileName))
                return false;
            return true;
        }

        private LocalizationStreamHandler getOuterType() {
            return LocalizationStreamHandler.this;
        }

    }

    private Map<Pair, File> fileMap = new HashMap<Pair, File>();

    /*
     * (non-Javadoc)
     * 
     * @see
     * com.raytheon.uf.common.serialization.comm.IRequestHandler#handleRequest
     * (com.raytheon.uf.common.serialization.comm.IServerRequest)
     */
    @Override
    public Object handleRequest(AbstractLocalizationStreamRequest request)
            throws Exception {
        Pair pair = new Pair(request.getContext(), request.getFileName());
        File file = fileMap.get(pair);

        if (file == null) {
            file = PathManagerFactory.getPathManager().getFile(
                    request.getContext(), request.getFileName());
            if (file == null) {
                throw new LocalizationException("File with name, "
                        + request.getFileName() + ", and context, "
                        + String.valueOf(request.getContext())
                        + ", could not be found");
            }
            fileMap.put(pair, file);
        }

        if (request instanceof LocalizationStreamGetRequest) {
            return handleStreamingGet((LocalizationStreamGetRequest) request,
                    file);
        } else if (request instanceof LocalizationStreamPutRequest) {
            LocalizationStreamPutRequest req = (LocalizationStreamPutRequest) request;
            LocalizationLevel protectedLevel = ProtectedFiles
                    .getProtectedLevel(req.getLocalizedSite(), request
                            .getContext().getLocalizationType(), request
                            .getFileName());
            if (protectedLevel != null
                    && protectedLevel != request.getContext()
                            .getLocalizationLevel()) {
                throw new LocalizationException("File: "
                        + request.getContext().getLocalizationType().toString()
                                .toLowerCase() + File.separator
                        + request.getFileName()
                        + " is protected and cannot be overridden");
            }
            return handleStreamingPut((LocalizationStreamPutRequest) request,
                    file);
        } else {
            throw new LocalizationException("Request type of "
                    + request.getClass() + " is not recognized");
        }
    }

    private Object handleStreamingGet(LocalizationStreamGetRequest request,
            File file) throws Exception {
        FileInputStream inputStream = null;
        try {
            inputStream = new FileInputStream(file);
            int bytesSkipped = 0;
            int toSkip = request.getOffset();

            // we are done if we the toSkip is 0, or toSkip increased in size
            while ((toSkip != 0) && (toSkip <= request.getOffset())) {
                bytesSkipped += inputStream.skip(toSkip);
                toSkip = request.getOffset() - bytesSkipped;
            }

            if (toSkip != 0) {
                throw new LocalizationException("Error skipping through file");
            }

            LocalizationStreamPutRequest response = new LocalizationStreamPutRequest();
            byte[] bytes = new byte[request.getNumBytes()];
            int bytesRead = inputStream.read(bytes, 0, request.getNumBytes());

            if (bytesRead == -1) {
                response.setBytes(new byte[0]);
                response.setEnd(true);
            } else {
                if (bytesRead != bytes.length) {
                    bytes = Arrays.copyOf(bytes, bytesRead);
                }

                response.setBytes(bytes);
                response.setEnd(request.getOffset() + bytesRead == file
                        .length());
            }
            return response;
        } finally {
            if (inputStream != null) {
                inputStream.close();
            }
        }
    }

    private Object handleStreamingPut(LocalizationStreamPutRequest request,
            File file) throws Exception {
        if (file.getParentFile().exists() == false) {
            file.getParentFile().mkdirs();
        }
        File tmpFile = new File(file.getParentFile(), file.getName() + "."
                + request.getId());
        if ((tmpFile.exists() == false) && (request.getOffset() != 0)) {
            throw new LocalizationException(
                    "Illegal state, request has offset set but file "
                            + "has not begun being written to yet.");
        } else if (tmpFile.exists()
                && (tmpFile.length() != request.getOffset())) {
            throw new LocalizationException(
                    "Illegal state, request's offset does not match size of file, size = "
                            + tmpFile.length() + " offset = "
                            + request.getOffset());
        }

        // if start of request, delete existing temporary file
        if (request.getOffset() == 0) {
            tmpFile.delete();
        }

        // if (
        if (tmpFile.exists() == false) {
            tmpFile.createNewFile();
        }
        FileOutputStream outputStream = null;
        Integer bytesWritten = 0;

        try {
            outputStream = new FileOutputStream(tmpFile, true);
            byte[] bytes = request.getBytes();
            bytesWritten += bytes.length;
            outputStream.write(bytes);
        } finally {
            if (outputStream != null) {
                outputStream.flush();
                outputStream.close();
            }
        }
        if (request.isEnd()) {
            FileChangeType changeType = FileChangeType.UPDATED;
            if (!file.exists()) {
                changeType = FileChangeType.ADDED;
            }

            tmpFile.renameTo(file);
            // tmpFile.delete();

            EDEXUtil.getMessageProducer().sendAsync(
                    UtilityManager.NOTIFY_ID,
                    new FileUpdatedMessage(request.getContext(), request
                            .getFileName(), changeType));
        }

        return bytesWritten;
    }

    @Override
    public AuthorizationResponse authorized(IUser user,
            AbstractLocalizationStreamRequest request) {
        if (request instanceof LocalizationStreamGetRequest) {
            // All gets are authorized
            return new AuthorizationResponse(true);
        } else if (request instanceof LocalizationStreamPutRequest) {
            LocalizationContext context = request.getContext();
            LocalizationLevel level = context.getLocalizationLevel();
            String fileName = request.getFileName();
            return getAuthorizationResponse(user, context, level, fileName,
                    request.getMyContextName());
        }
        return new AuthorizationResponse(true);
    }
}