/*
 * Copyright (C) 2009 eXo Platform SAS.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.exoplatform.services.ftp.command;

import org.exoplatform.commons.utils.MimeTypeResolver;
import org.exoplatform.services.ftp.FtpConst;
import org.exoplatform.services.ftp.FtpTextUtils;
import org.exoplatform.services.ftp.config.FtpConfig;
import org.exoplatform.services.jcr.util.IdGenerator;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.Calendar;

import javax.jcr.Node;
import javax.jcr.PathNotFoundException;
import javax.jcr.RepositoryException;
import javax.jcr.Session;

/**
 * Created by The eXo Platform SAS Author : Vitaly Guly gavrik-vetal@ukr.net/mail.ru
 * 
 * @version $Id: CmdStor.java 34445 2009-07-24 07:51:18Z dkatayev $
 */

public class CmdStor extends FtpCommandImpl
{

   private static final Log LOG = ExoLogger.getLogger("exo.jcr.component.ftp.CmdStor");

   public CmdStor()
   {
      commandName = FtpConst.Commands.CMD_STOR;
   }

   public void run(String[] params) throws IOException
   {
      if (clientSession().getDataTransiver() == null)
      {
         reply(FtpConst.Replyes.REPLY_425);
         return;
      }

      try
      {
         while (!clientSession().getDataTransiver().isConnected())
         {
            Thread.sleep(100);
         }

      }
      catch (Exception exc)
      {
         LOG.info("Unhandled exception. " + exc.getMessage(), exc);
      }

      if (params.length < 2)
      {
         reply(String.format(FtpConst.Replyes.REPLY_500_PARAMREQUIRED, FtpConst.Commands.CMD_STOR));
         return;
      }

      String fileName = params[1];

      try
      {
         ArrayList<String> newPath = clientSession().getFullPath(fileName);
         
         FtpConfig ftpConfig = clientSession().getFtpServer().getConfiguration();
         if (ftpConfig.isReplaceForbiddenChars())
         {
            String fName = newPath.get(newPath.size()-1);
            String newfName =
                     FtpTextUtils.replaceForbiddenChars(fName, ftpConfig.getForbiddenChars(), ftpConfig
                              .getReplaceChar());
            
            fileName  = fileName.substring(0, fileName.indexOf(fName)) + newfName;
         }
         
         Session curSession = clientSession().getSession(newPath.get(0));

         Node resourceNode = getExistedFileNode(curSession, fileName);

         boolean isNeedCheckIn = false;
         if (resourceNode == null)
         {
            if (FtpConst.Commands.CMD_REST.equals(clientSession().getPrevCommand()))
            {
               reply(String.format(FtpConst.Replyes.REPLY_550, "Requested file not exist"));
               return;
            }

            resourceNode = createNewFileNode(curSession, fileName);

         }
         else
         {
            if (FtpConst.Commands.CMD_REST.equals(clientSession().getPrevCommand()))
            {
               int restOffset = new Integer(clientSession().getPrevParams());

               if (restOffset > (resourceNode.getProperty(FtpConst.NodeTypes.JCR_DATA).getLength() + 1))
               {
                  reply(String.format(FtpConst.Replyes.REPLY_550, "Restore value invalid"));
                  return;
               }

            }

            if (resourceNode.getParent().isNodeType(FtpConst.NodeTypes.MIX_VERSIONABLE))
            {
               resourceNode.getParent().checkout();
               isNeedCheckIn = true;
            }
         }

         InputStream inputStream = null;
         String cacheFileName = null;
         if (FtpConst.Commands.CMD_REST.equals(clientSession().getPrevCommand()))
         {
            String cacheFolderName = clientSession().getFtpServer().getConfiguration().getCacheFolderName();
            cacheFileName = cacheFolderName + "/" + IdGenerator.generate() + FtpConst.FTP_CACHEFILEEXTENTION;

            File cacheFile = new File(cacheFileName);
            boolean created = cacheFile.createNewFile();
            if (!created)
            {
               reply(String.format(FtpConst.Replyes.REPLY_550, "STOR"));
               return;
            }

            FileOutputStream cacheOutStream = new FileOutputStream(cacheFile);
            InputStream nodeInputStream = resourceNode.getProperty(FtpConst.NodeTypes.JCR_DATA).getStream();

            if (nodeInputStream == null)
            {
               reply(String.format(FtpConst.Replyes.REPLY_550, "STOR"));
               return;
            }

            byte[] buffer = new byte[32 * 1024];

            while (true)
            {
               int readed = nodeInputStream.read(buffer);
               if (readed < 0)
               {
                  break;
               }
               cacheOutStream.write(buffer, 0, readed);
            }

            cacheOutStream.close();

            RandomAccessFile cacheFilePoint = new RandomAccessFile(cacheFile, "rw");
            int restOffset = new Integer(clientSession().getPrevParams());

            cacheFilePoint.seek(restOffset);

            InputStream socketInputStream = clientSession().getDataTransiver().getInputStream();

            reply(FtpConst.Replyes.REPLY_125);

            while (true)
            {
               int readed = socketInputStream.read(buffer);
               if (readed < 0)
               {
                  break;
               }
               cacheFilePoint.write(buffer, 0, readed);
            }

            cacheFilePoint.close();

            inputStream = new FileInputStream(cacheFile);

         }
         else
         {
            inputStream = clientSession().getDataTransiver().getInputStream();
            reply(FtpConst.Replyes.REPLY_125);
         }

         resourceNode.setProperty(FtpConst.NodeTypes.JCR_LASTMODIFIED, Calendar.getInstance());

         resourceNode.setProperty(FtpConst.NodeTypes.JCR_DATA, inputStream);

         curSession.save();

         clientSession().closeDataTransiver();

         try
         {
            inputStream.close();
         }
         catch (IOException exc)
         {
            LOG.info("Failurinc closing input stream");
         }

         if (cacheFileName != null)
         {
            File cacheFile = new File(cacheFileName);
            cacheFile.delete();
         }

         if (isNeedCheckIn)
         {
            resourceNode.getParent().checkin();
         }

         reply(FtpConst.Replyes.REPLY_226);
         return;
      }
      catch (RepositoryException rexc)
      {
         if (LOG.isTraceEnabled())
         {
            LOG.trace("An exception occurred: " + rexc.getMessage());
         }
      }
      catch (Exception exc)
      {
         LOG.info("Unhandled exception. " + exc.getMessage(), exc);
      }

      clientSession().closeDataTransiver();
      reply(String.format(FtpConst.Replyes.REPLY_550, fileName));
   }

   protected Node getExistedFileNode(Session curSession, String resName)
   {
      try
      {
         ArrayList<String> newPath = clientSession().getFullPath(resName);
         String repoPath = clientSession().getRepoPath(newPath);

         Node fileNode = (Node)curSession.getItem(repoPath);

         return fileNode.getNode(FtpConst.NodeTypes.JCR_CONTENT);
      }
      catch (RepositoryException rexc)
      {
         if (LOG.isTraceEnabled())
         {
            LOG.trace("An exception occurred: " + rexc.getMessage());
         }
      }
      catch (Exception exc)
      {
         LOG.info("Unhandled exception. " + exc.getMessage(), exc);
      }
      return null;
   }

   protected Node createNewFileNode(Session curSession, String resName)
   {
      try
      {
         ArrayList<String> newPath = clientSession().getFullPath(resName);

         String repoPath = clientSession().getRepoPath(newPath);
         String onlyName = repoPath.substring(repoPath.lastIndexOf("/") + 1);
         String onlyPath = repoPath.substring(0, repoPath.length() - onlyName.length());

         if (onlyPath.length() > 1)
         {
            if (onlyPath.endsWith("/"))
            {
               onlyPath = onlyPath.substring(0, onlyPath.length() - 1);
            }
         }

         Node parentNode = (Node)curSession.getItem(onlyPath);

         FtpConfig configuration = clientSession().getFtpServer().getConfiguration();

         String fileNodeType = configuration.getDefFileNodeType();

         Node fileNode = parentNode.addNode(onlyName, fileNodeType);
         Node dataNode = fileNode.addNode(FtpConst.NodeTypes.JCR_CONTENT, FtpConst.NodeTypes.NT_RESOURCE);

         MimeTypeResolver mimeTypeResolver = new MimeTypeResolver();
         mimeTypeResolver.setDefaultMimeType(configuration.getDefFileMimeType());
         String mimeType = mimeTypeResolver.getMimeType(onlyName);

         dataNode.setProperty(FtpConst.NodeTypes.JCR_MIMETYPE, mimeType);

         dataNode.setProperty(FtpConst.NodeTypes.JCR_LASTMODIFIED, Calendar.getInstance());

         return dataNode;
      }
      catch (PathNotFoundException pexc)
      {
         if (LOG.isTraceEnabled())
         {
            LOG.trace("An exception occurred: " + pexc.getMessage());
         }
      }
      catch (Exception exc)
      {
         LOG.info("Unhandled exception. " + exc.getMessage(), exc);
      }
      return null;
   }

}
