//--------------------------------------------------------------------------------------------------------------------
// <copyright file="LqrSMACDomainSocket.h" company="LiquidRobotics">
//
// Copyright (c) 2014 Liquid Robotics, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// This software is associated with a release.  Liquid Robotics makes no
// representation that the design, interfaces or component distribution will
// remain unchanged in future releases.
//
// </copyright>
//
// <summary>
//   Declarations for the DomainSocket class, which encapsulates a Unix domain socket
//   (i.e. a socket which is identified with a string-based name that is local to the
//   machine)
//
//   Example:
//
//		#include <iostream>
//		#include <boost/algorithm/string.hpp>
//		#include "DomainSocket.h"
//		#include "LqrSMACByteArray.h"
//		#include "LqrSMACSemaphore.h"
//
//		class MyClient
//		{
//		public:
//
//			MyClient(const string& _connectionName)
//			: sock(_connectionName, 1024)
//			{
//			}
//
//			static void Run()
//			{
//				MyClient client("socktest");
//				client.Invoke("Hello, world!");
//			}
//
//			void Invoke(const string& request)
//			{
//				sock.ConnectToServer();
//				sock.Write(request, true);
//
//				string s;
//				unsigned n = 1;
//
//				try
//				{
//					do
//					{
//						sock.Read(s);
//						cout << n++ << " " << s.length() << " bytes: " << s << endl;
//					} while(s.length());
//				}
//				catch(...)
//				{
//				}
//			}
//
//		private:
//
//			DomainSocket sock;
//			DomainSocket::ScopedConnectionPtr sockConn;
//		};
//
//		class MyServer
//		{
//		public:
//
//			MyServer(const string& _connectionName)
//			: sock(_connectionName, 1024)
//			{
//			}
//
//			static void Run()
//			{
//				MyServer server("socktest");
//
//				server.Invoke();
//			}
//
//			void Invoke()
//			{
//				DomainSocket::ScopedConnectionPtr subscription = sock.Subscribe(boost::bind(&MyServer::Callback, this, _1, _2));
//				sock.StartServer();
//
//				Semaphore sem;
//				sem.SetCount(0);
//				sem.Wait();
//
//				sock.StopServer();
//			}
//
//		private:
//
//			void Callback(DomainSocket& sender, DomainSocket::Ptr newSocket)
//			{
//				string input;
//
//				newSocket->Read(input);
//				newSocket->Write(string("1 ")+input);
//				sleep(1);
//				newSocket->Write(string("2 ")+input);
//				sleep(1);
//				newSocket->Write(string("3 ")+input);
//				sleep(1);
//			}
//
//			DomainSocket sock;
//			DomainSocket::ScopedConnectionPtr sockConn;
//		};
//
//		int main(int argc, char* argv[])
//		{
//			if ((argc == 2) && boost::iequals(argv[1], "--server"))
//				MyServer::Run();
//			else if ((argc == 2) && boost::iequals(argv[1], "--client"))
//				MyClient::Run();
//			else
//				cerr << "Invalid arguments" << endl;
//
//			return 0;
//		}
// </summary>
//
// <owner>Mike Cookson</owner>
//--------------------------------------------------------------------------------------------------------------------

#ifndef __LQRSMACDOMAINSOCKET_H__
#define __LQRSMACDOMAINSOCKET_H__

#include <stdexcept>
#include <string>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <boost/signals2.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/thread.hpp>
#include "LqrSMACPipes.h"
#include "LqrSMACSemaphore.h"
#include "LqrSMACSharedLib.h"

namespace LiquidRobotics {
namespace SMACFramework {

class DomainSocket
{
public:

	typedef boost::shared_ptr<DomainSocket> Ptr;
	typedef std::string string;
	typedef boost::signals2::scoped_connection ScopedConnection;
	typedef boost::shared_ptr<ScopedConnection> ScopedConnectionPtr;
	typedef boost::signals2::signal<void (DomainSocket& sender, Ptr newSocket)> SignalType;

    class SHARED_LIB_PUBLIC SystemError : public std::runtime_error
    {
    public:
    	SystemError(const string& whatArg);
    	SystemError(const string& whatArg, int _errNum);
    	const int errNum;
    	inline string describe() const { return describe(errNum); }
    	static string describe(int errNum);
    };

	DomainSocket(const string& _advertisedName, unsigned _readWriteSize = 4096);
	virtual ~DomainSocket();

	void ConnectToServer();
	const string& GetAdvertisedName() const;
	int GetFileDescriptor() const;
	Ptr ListenForConnection(int ms = -1);
	void Read(string& data);
	void ShutdownWrites();
	void StartServer(bool enableCallbacks = true, int _numRequests = 3);
	void StopServer();
	ScopedConnectionPtr Subscribe(SignalType::slot_type callback);
	void Write(const string& data, bool shutdown = false);
	void Write(const void* pData, unsigned numBytes, bool shutdown = false);

private:

	typedef LiquidRobotics::SMACFramework::Pipes Pipes;
	typedef LiquidRobotics::SMACFramework::Semaphore Semaphore;
	typedef boost::thread Thread;
	typedef boost::shared_ptr<Thread> ThreadPtr;

	void ListenerThread();
	static void MakeAddress(const string& advertisedName, sockaddr_un& sa, socklen_t& salen);

	static int Wait(int fd0, int fd1, int ms);

	const string advertisedName;
	const unsigned readWriteSize;
	SignalType sig;
	int socketfd;
	int numRequests;
	bool listenerRunning;
	sockaddr_un sa;
	socklen_t salen;
	Semaphore startSem;
	ThreadPtr listeningThread;
	Pipes pipes;
};

}
}

#endif
