/* $Id$ */
/* 
 * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
 * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
 */
#ifndef __OS_SYMBIAN_H__
#define __OS_SYMBIAN_H__

#include <pj/assert.h>
#include <pj/errno.h>
#include <pj/sock.h>
#include <pj/os.h>
#include <pj/string.h>

#include <e32base.h>
#include <e32cmn.h>
#include <e32std.h>
#include <es_sock.h>
#include <in_sock.h>
#include <charconv.h>
#include <utf.h>
#include <e32cons.h>

// Forward declarations
class CPjSocketReader;

#ifndef PJ_SYMBIAN_TIMER_PRIORITY
#    define PJ_SYMBIAN_TIMER_PRIORITY	EPriorityNormal
#endif

//
// PJLIB Symbian's Socket
//
class CPjSocket
{
public:
    enum
    {
	MAX_LEN = 1500,
    };

    // Construct CPjSocket
    CPjSocket(int af, int sock_type, RSocket &sock)
	: af_(af), sock_(sock), sock_type_(sock_type), connected_(false), 
	  sockReader_(NULL)
    { 
    }

    // Destroy CPjSocket
    ~CPjSocket();

    // Get address family
    int GetAf() const 
    {
    	return af_;	
    }
    
    // Get the internal RSocket
    RSocket& Socket()
    {
	return sock_;
    }

    // Get socket connected flag.
    bool IsConnected() const
    {
	return connected_;
    }

    // Set socket connected flag.
    void SetConnected(bool connected)
    {
	connected_ = connected;
    }

    // Get socket type
    int GetSockType() const
    {
	return sock_type_;
    }
    
    // Returns true if socket is a datagram
    bool IsDatagram() const
    {
	return sock_type_ == KSockDatagram;
    }
    
    // Get socket reader, if any.
    // May return NULL.
    CPjSocketReader *Reader()
    {
	return sockReader_;
    }

    // Create socket reader.
    CPjSocketReader *CreateReader(unsigned max_len=CPjSocket::MAX_LEN);

    // Delete socket reader when it's not wanted.
    void DestroyReader();
    
private:
    int		     af_;
    RSocket	     sock_;	    // Must not be reference, or otherwise
				    // it may point to local variable!
    unsigned   	     sock_type_;
    
    bool	     connected_;
    CPjSocketReader *sockReader_;
};


//
// Socket reader, used by select() and ioqueue abstraction
//
class CPjSocketReader : public CActive
{
public:
    // Construct.
    static CPjSocketReader *NewL(CPjSocket &sock, unsigned max_len=CPjSocket::MAX_LEN);

    // Destroy;
    ~CPjSocketReader();

    // Start asynchronous read from the socket.
    void StartRecv(void (*cb)(void *key)=NULL, 
		   void *key=NULL, 
		   TDes8 *aDesc = NULL,
		   TUint flags = 0);

    // Start asynchronous read from the socket.
    void StartRecvFrom(void (*cb)(void *key)=NULL, 
		       void *key=NULL, 
		       TDes8 *aDesc = NULL,
		       TUint flags = 0,
		       TSockAddr *fromAddr = NULL);

    // Cancel asynchronous read.
    void DoCancel();

    // Implementation: called when read has completed.
    void RunL();

    // Check if there's pending data.
    bool HasData() const
    {
	return buffer_.Length() != 0;
    }

    // Append data to aDesc, up to aDesc's maximum size.
    // If socket is datagram based, buffer_ will be clared.
    void ReadData(TDes8 &aDesc, TInetAddr *addr=NULL);

private:
    CPjSocket	   &sock_;
    bool	    isDatagram_;
    TPtr8	    buffer_;
    TInetAddr	    recvAddr_;

    void	   (*readCb_)(void *key);
    void	    *key_;

    //
    // Constructor
    //
    CPjSocketReader(CPjSocket &sock);
    void ConstructL(unsigned max_len);
};



//
// Time-out Timer Active Object
//
class CPjTimeoutTimer : public CActive
{
public:
    static CPjTimeoutTimer *NewL();
    ~CPjTimeoutTimer();

    void StartTimer(TUint miliSeconds);
    bool HasTimedOut() const;

protected:
    virtual void RunL();
    virtual void DoCancel();
    virtual TInt RunError(TInt aError);

private:
    RTimer	timer_;
    pj_bool_t	hasTimedOut_;

    CPjTimeoutTimer();
    void ConstructL();
};



//
// Symbian OS helper for PJLIB
//
class PjSymbianOS
{
public:
    //
    // Get the singleton instance of PjSymbianOS
    //
    static PjSymbianOS *Instance();

    //
    // Set parameters
    //
    void SetParameters(pj_symbianos_params *params);
    
    //
    // Initialize.
    //
    TInt Initialize();

    //
    // Shutdown.
    //
    void Shutdown();


    //
    // Socket helper.
    //

    // Get RSocketServ instance to be used by all sockets.
    RSocketServ &SocketServ()
    {
	return appSocketServ_ ? *appSocketServ_ : socketServ_;
    }

    // Get RConnection instance, if any.
    RConnection *Connection() 
    {
    	return appConnection_;
    }
    
    // Convert TInetAddr to pj_sockaddr_in
    static inline pj_status_t Addr2pj(const TInetAddr & sym_addr,
			       	      pj_sockaddr &pj_addr,
			       	      int *addr_len,
			       	      pj_bool_t convert_ipv4_mapped_addr = PJ_FALSE)
    {
    TUint fam = sym_addr.Family();
	pj_bzero(&pj_addr, *addr_len);
	if (fam == PJ_AF_INET || 
			(convert_ipv4_mapped_addr && 
			 fam == PJ_AF_INET6 && 
			 sym_addr.IsV4Mapped())) 
	{
		pj_addr.addr.sa_family = PJ_AF_INET;
	    PJ_ASSERT_RETURN(*addr_len>=(int)sizeof(pj_sockaddr_in), PJ_ETOOSMALL);
	    pj_addr.ipv4.sin_addr.s_addr = pj_htonl(sym_addr.Address());
	    pj_addr.ipv4.sin_port = pj_htons((pj_uint16_t) sym_addr.Port());
	    *addr_len = sizeof(pj_sockaddr_in);
	} else if (fam == PJ_AF_INET6) {
	    PJ_ASSERT_RETURN(*addr_len>=(int)sizeof(pj_sockaddr_in6), PJ_ETOOSMALL);
	    const TIp6Addr & ip6 = sym_addr.Ip6Address();
	    pj_addr.addr.sa_family = PJ_AF_INET6;
	    pj_memcpy(&pj_addr.ipv6.sin6_addr, ip6.u.iAddr8, 16);
	    pj_addr.ipv6.sin6_port = pj_htons((pj_uint16_t) sym_addr.Port());
	    pj_addr.ipv6.sin6_scope_id = pj_htonl(sym_addr.Scope());
	    pj_addr.ipv6.sin6_flowinfo = pj_htonl(sym_addr.FlowLabel());
	    *addr_len = sizeof(pj_sockaddr_in6);
	} else {
	    pj_assert(!"Unsupported address family");
	    return PJ_EAFNOTSUP;
	}
	
	return PJ_SUCCESS;
    }


    // Convert pj_sockaddr_in to TInetAddr
    static inline pj_status_t pj2Addr(const pj_sockaddr &pj_addr,
    				      int addrlen,
			       	      TInetAddr & sym_addr)
    {
    	if (pj_addr.addr.sa_family == PJ_AF_INET) {
    	    PJ_ASSERT_RETURN(addrlen >= (int)sizeof(pj_sockaddr_in), PJ_EINVAL);
	    sym_addr.Init(KAfInet);
    	    sym_addr.SetAddress((TUint32)pj_ntohl(pj_addr.ipv4.sin_addr.s_addr));
    	    sym_addr.SetPort(pj_ntohs(pj_addr.ipv4.sin_port));
    	} else if (pj_addr.addr.sa_family == PJ_AF_INET6) {
    	    TIp6Addr ip6;
    	
    	    PJ_ASSERT_RETURN(addrlen>=(int)sizeof(pj_sockaddr_in6), PJ_EINVAL);
    	    pj_memcpy(ip6.u.iAddr8, &pj_addr.ipv6.sin6_addr, 16);
    	    sym_addr.Init(KAfInet6);
    	    sym_addr.SetAddress(ip6);
    	    sym_addr.SetScope(pj_ntohl(pj_addr.ipv6.sin6_scope_id));
    	    sym_addr.SetFlowLabel(pj_ntohl(pj_addr.ipv6.sin6_flowinfo));
    	} else {
    	    pj_assert(!"Unsupported address family");
    	}
    	return PJ_SUCCESS;
    }


    //
    // Resolver helper
    //

    // Get RHostResolver instance
    RHostResolver & GetResolver(int af)
    {
    	if (af==PJ_AF_INET6) {
    	    return appHostResolver6_ ? *appHostResolver6_ : hostResolver6_;
    	} else {
    	    return appHostResolver_ ? *appHostResolver_ : hostResolver_;
    	}
    }

    //
    // Return true if the access point connection is up
    //
    bool IsConnectionUp() const
    {
	return isConnectionUp_;
    }

    //
    // Set access point connection status
    //
    void SetConnectionStatus(bool up)
    {
	isConnectionUp_ = up;
    }

    //
    // Unicode Converter
    //

    // Convert to Unicode
    TInt ConvertToUnicode(TDes16 &aUnicode, const TDesC8 &aForeign);

    // Convert from Unicode
    TInt ConvertFromUnicode(TDes8 &aForeign, const TDesC16 &aUnicode);

    //
    // Get console
    //
    
    // Get console
    CConsoleBase *Console()
    {
	return console_;
    }
    
    //
    // Get select() timeout timer.
    //
    CPjTimeoutTimer *SelectTimeoutTimer()
    {
	return selectTimeoutTimer_;
    }

    //
    // Wait for any active objects to run.
    //
    void WaitForActiveObjects(TInt aPriority = CActive::EPriorityStandard)
    {
	TInt aError;
	CActiveScheduler::Current()->WaitForAnyRequest();
	CActiveScheduler::RunIfReady(aError, aPriority);
    }

private:
    bool isConnectionUp_;
    
    bool isSocketServInitialized_;
    RSocketServ socketServ_;

    bool isResolverInitialized_;
    RHostResolver hostResolver_;
    RHostResolver hostResolver6_;

    CConsoleBase* console_;

    CPjTimeoutTimer *selectTimeoutTimer_;

    // App parameters
    RSocketServ *appSocketServ_;
    RConnection *appConnection_;
    RHostResolver *appHostResolver_;
    RHostResolver *appHostResolver6_;
    
private:
    PjSymbianOS();
};

// This macro is used to check the access point connection status and return
// failure if the AP connection is down or unusable. See the documentation
// of pj_symbianos_set_connection_status() for more info
#define PJ_SYMBIAN_CHECK_CONNECTION() \
    PJ_SYMBIAN_CHECK_CONNECTION2(PJ_ECANCELLED)

#define PJ_SYMBIAN_CHECK_CONNECTION2(retval) \
    do { \
	if (!PjSymbianOS::Instance()->IsConnectionUp()) \
	    return retval; \
    } while (0);

#endif	/* __OS_SYMBIAN_H__ */

