/*
 * 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.client;

import org.exoplatform.container.ExoContainer;
import org.exoplatform.container.ExoContainerContext;
import org.exoplatform.services.ftp.FtpConst;
import org.exoplatform.services.ftp.FtpServer;
import org.exoplatform.services.ftp.data.FtpDataTransiver;
import org.exoplatform.services.jcr.ext.common.SessionProvider;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.exoplatform.services.security.Authenticator;
import org.exoplatform.services.security.ConversationState;
import org.exoplatform.services.security.Credential;
import org.exoplatform.services.security.Identity;
import org.exoplatform.services.security.IdentityRegistry;
import org.exoplatform.services.security.PasswordCredential;
import org.exoplatform.services.security.UsernameCredential;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.ArrayList;

import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.security.auth.login.LoginException;

/**
 * Created by The eXo Platform SAS Author : Vitaly Guly <gavrik-vetal@ukr.net/mail.ru>
 * 
 * @version $Id: $
 */

public class FtpClientSessionImpl implements FtpClientSession
{

   private static Log log = ExoLogger.getLogger(FtpConst.FTP_PREFIX + "FtpClientSessionImpl");

   private FtpServer ftpServer;

   FtpClientCommandThread commandThread;

   FtpClientTimeOutThread timeOutThread;

   private FtpDataTransiver transiver = null;

   private Socket clientSocket;

   // private PrintStream outPrintStream;

   private SessionProvider sessionFactory;

   private ArrayList<String> path = new ArrayList<String>();

   private String serverIp;

   private String userName = "";

   private String userPass = "";

   private boolean logged = false;

   private String prevCommand = "";

   private String prevParams = "";

   private String prevParamsEx = "";

   private String userId;

   public FtpClientSessionImpl(FtpServer ftpServer, Socket clientSocket) throws Exception
   {
      this.ftpServer = ftpServer;
      this.clientSocket = clientSocket;
      // outPrintStream = new PrintStream(clientSocket.getOutputStream());

      SocketAddress addr = clientSocket.getLocalSocketAddress();
      String serverAddr = addr.toString();
      if (serverAddr.startsWith("/"))
      {
         serverAddr = serverAddr.substring(1);
      }
      String[] serverLocations = serverAddr.split(":");
      serverIp = serverLocations[0];

      welcomeClient();

      commandThread = new FtpClientCommandThread(this);
      commandThread.start();

      if (getFtpServer().getConfiguration().isNeedTimeOut())
      {
         timeOutThread = new FtpClientTimeOutThread(this);
         timeOutThread.start();
      }
   }

   public Socket getClientSocket()
   {
      return clientSocket;
   }

   public void reply(String replyString) throws IOException
   {
      String encodingType = ftpServer.getConfiguration().getClientSideEncoding();
      try
      {
         byte[] data = replyString.getBytes(encodingType);
         // outPrintStream.println(new String(, encodingType));
         clientSocket.getOutputStream().write(data);
      }
      catch (UnsupportedEncodingException eexc)
      {
         log.info("Unsupported encoding exception. See for CLIENT-SIDE-ENCODING parameter. " + eexc.getMessage(), eexc);
         byte[] data = replyString.getBytes();
         clientSocket.getOutputStream().write(data);
         // outPrintStream.println(replyString);
      }
      clientSocket.getOutputStream().write("\r\n".getBytes());
   }

   public FtpServer getFtpServer()
   {
      return ftpServer;
   }

   protected void welcomeClient() throws IOException
   {
      for (int i = 0; i < FtpConst.EXO_LOGO.length; i++)
      {
         reply(FtpConst.EXO_LOGO[i]);
      }
   }

   private boolean isLoggedOut = false;

   public void logout()
   {
      if (isLoggedOut)
      {
         return;
      }
      isLoggedOut = true;

      commandThread.interrupt();
      if (timeOutThread != null)
      {
         timeOutThread.interrupt();
      }

      closeDataTransiver();

      try
      {
         clientSocket.close();
      }
      catch (Exception exc)
      {
         log.info("Unhandled exception. " + exc.getMessage(), exc);
      }

      getFtpServer().unRegisterClient(this);

      if (sessionFactory != null)
      {
         sessionFactory.close();
      }

      this.unRegistrateIdentity();
   }

   public boolean isLogged()
   {
      return logged;
   }

   public void setUserName(String userName)
   {
      this.userName = userName;
      logged = false;
   }

   public void setPassword(String userPass) throws Exception
   {
      this.userPass = userPass;

      if (sessionFactory != null)
      {
         sessionFactory.close();
      }

      ConversationState state = getConversationState();
      ConversationState.setCurrent(state);
      this.sessionFactory = new SessionProvider(state);

      logged = true;
   }

   public String getUserName()
   {
      return userName;
   }

   public String getUserPassword()
   {
      return userPass;
   }

   public String getServerIp()
   {
      return serverIp;
   }

   public void setDataTransiver(FtpDataTransiver newTransiver)
   {
      if (transiver != null)
      {
         transiver.close();
      }
      transiver = newTransiver;
   }

   public void closeDataTransiver()
   {
      if (transiver != null)
      {
         transiver.close();
         transiver = null;
      }
   }

   public FtpDataTransiver getDataTransiver()
   {
      return transiver;
   }

   public void setPrevCommand(String prevCommand)
   {
      this.prevCommand = prevCommand;
   }

   public void setPrevParams(String prevParams)
   {
      this.prevParams = prevParams;
   }

   public void setPrevParamsEx(String prevParamsEx)
   {
      this.prevParamsEx = prevParamsEx;
   }

   public String getPrevCommand()
   {
      return prevCommand;
   }

   public String getPrevParams()
   {
      return prevParams;
   }

   public String getPrevParamsEx()
   {
      return prevParamsEx;
   }

   public ArrayList<String> getFullPath(String resPath)
   {
      ArrayList<String> curPath = getPath();
      if (resPath.startsWith("/"))
      {
         curPath.clear();
      }

      String[] pathes = resPath.split("/");
      for (int i = 0; i < pathes.length; i++)
      {
         if (!"".equals(pathes[i]))
         {
            if ("..".equals(pathes[i]))
            {
               if (curPath.size() != 0)
               {
                  curPath.remove(curPath.size() - 1);
               }
            }
            else
            {
               curPath.add(pathes[i]);
            }
         }
      }
      return curPath;
   }

   public String getRepoPath(ArrayList<String> repoPath)
   {
      String curPath = "/";
      for (int i = 1; i < repoPath.size(); i++)
      {
         curPath += repoPath.get(i);
         if (i < (repoPath.size() - 1))
         {
            curPath += "/";
         }
      }
      return curPath;
   }

   public Session getSession(String workspaceName) throws Exception
   {
      if (ftpServer.getRepository() == null)
      {
         throw new RepositoryException("Repository can not be retrieved.");
      }
      Session curSession = sessionFactory.getSession(workspaceName, ftpServer.getRepository());
      curSession.refresh(false);
      return curSession;
   }

   public String changePath(String resPath)
   {
      ArrayList<String> newPath = getFullPath(resPath);

      if (newPath.size() == 0)
      {
         path = new ArrayList<String>();
         return FtpConst.Replyes.REPLY_250;
      }

      String repoWorkspace = newPath.get(0);
      String repoPath = getRepoPath(newPath);

      try
      {
         Session curSession = getSession(repoWorkspace);

         Node curNode = (Node)curSession.getItem(repoPath);
         if (curNode.isNodeType(FtpConst.NodeTypes.NT_FILE))
         {
            return FtpConst.Replyes.REPLY_550;
         }

         path = (ArrayList<String>)newPath.clone();
         return FtpConst.Replyes.REPLY_250;
      }
      catch (RepositoryException exc)
      {
         return FtpConst.Replyes.REPLY_550;
      }
      catch (Exception exc)
      {
         log.info("Unhandled exception. " + exc.getMessage(), exc);
      }

      return FtpConst.Replyes.REPLY_550;
   }

   public ArrayList<String> getPath()
   {
      return (ArrayList<String>)path.clone();
   }

   public void refreshTimeOut()
   {
      if (timeOutThread != null)
      {
         timeOutThread.refreshTimeOut();
      }
   }

   private ConversationState getConversationState() throws Exception
   {
      ExoContainer container = ExoContainerContext.getCurrentContainer();
      Authenticator authenticator = (Authenticator)container.getComponentInstanceOfType(Authenticator.class);

      IdentityRegistry identityRegistry =
         (IdentityRegistry)container.getComponentInstanceOfType(IdentityRegistry.class);

      if (authenticator == null)
         throw new LoginException("No Authenticator component found, check your configuration");

      Credential[] credentials =
         new Credential[]{new UsernameCredential(this.userName), new PasswordCredential(this.userPass)};

      this.userId = authenticator.validateUser(credentials);
      Identity identity = authenticator.createIdentity(this.userId);
      identityRegistry.register(identity);

      ConversationState state = new ConversationState(identity);
      // keep subject as attribute in ConversationState
      state.setAttribute(ConversationState.SUBJECT, identity.getSubject());

      return state;
   }

   private void unRegistrateIdentity()
   {
      ConversationState.setCurrent(null);

      ExoContainer container = ExoContainerContext.getCurrentContainer();
      IdentityRegistry identityRegistry =
         (IdentityRegistry)container.getComponentInstanceOfType(IdentityRegistry.class);

      // The check need for case when login failed
      if (this.userId != null)
      {
         identityRegistry.unregister(this.userId);
      }
   }
}
