/*
 * HA_server.cpp
 *
 */
#include <unistd.h>
#include <pthread.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/in.h>

#include <memory>

#include "FLAC/metadata.h"
#include "FLAC/stream_encoder.h"


#include "HA_server.h"
#ifdef ENABLE_JNI_FOR_ANDROID
#include "HA_java.h"
#endif // ENABLE_JNI_FOR_ANDROID
#include "HA_port.h"
#include "HA_log.h"
#include "HA_ControlObject.h"

namespace HARKAndroid {

struct socks{
	const int port;
	const int fd;
	socks();
	socks(int aport, int afd) : port(aport), fd(afd) {}
	//~socks() {		LOGD("destruct %d %d", port, fd);}
};

}

static HARKAndroid::EventFlag server_start_ok;

static void progress_callback(const FLAC__StreamEncoder *encoder,
			      FLAC__uint64 bytes_written,
			      FLAC__uint64 samples_written,
			      unsigned frames_written,
			      unsigned total_frames_estimate,
			      void *client_data)
{
  (void)encoder, (void)client_data;
  
  //fprintf(stderr, "wrote %llu bytes, %llu samples, %u frames\n", (unsigned long long)bytes_written, (unsigned long long)samples_written, total_frames_estimate);
}

static void *flac_mic_data_loop(void *arg)
{
  
  std::vector<HARKAndroid::port_fd_pair> fds;

  for(int i = 0; i < HA_PORT_A_FLAC_DATA_NUM; i++) {

    int port = HA_PORT_A_FLAC_DATA_ORG + i;

    int fd = ::socket(AF_INET, SOCK_STREAM, 0);
    if(fd <= 0){
      LOGE("socket Error");
      return(NULL);
    }

    const int reuseaddr = 1;
    ::setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr));
    
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr   = INADDR_ANY;
    addr.sin_port   = htons(port);
    
    if(::bind(fd, (struct sockaddr *) &addr, sizeof(addr)) != 0){
      ::close(fd);
      LOGE("bind Error");
      return(NULL);
    }
    
    if(::listen(fd, 5) != 0){
      ::close(fd);
      LOGE("listen Error");
      return(NULL);
    }
  
    HARKAndroid::HARKControl->AddServerSocket(port, fd);

    HARKAndroid::port_fd_pair tmp;
    tmp.port = port;
    tmp.fd = fd;

    fds.push_back(tmp);

  }

  server_start_ok.Set();

  int fd_max = fds[0].fd;

  for(std::vector<HARKAndroid::port_fd_pair>::iterator iter = fds.begin(); iter != fds.end(); iter++) {
    if (fd_max < iter->fd) {
      fd_max = iter->fd;
    }
  }

  fd_set rfds;
  FD_ZERO(&rfds);
  for(std::vector<HARKAndroid::port_fd_pair>::iterator iter = fds.begin(); iter != fds.end(); iter++) {
    FD_SET(iter->fd, &rfds);
  }

  while(1) {

    int ret = ::select(fd_max + 1, &rfds, NULL, NULL, NULL);
    if(ret < 0){
      if(errno == EINTR){
	continue;
      }
      else{
	LOGE("select Error");
	break;
      }
    }
    else if(ret > 0){

      for(std::vector<HARKAndroid::port_fd_pair>::iterator iter = fds.begin(); iter != fds.end(); iter++) {

	if (FD_ISSET(iter->fd, &rfds)) {

	  struct sockaddr_in caddr;
	  int client_len = sizeof(caddr);
	  int csock_fd = ::accept(iter->fd, (struct sockaddr *)&caddr, (socklen_t*)&client_len);
	  if(csock_fd != -1){

	    const unsigned int pu = HARKAndroid::HARKControl->flac_mic_get_port_u(iter->port - HA_PORT_A_FLAC_DATA_ORG);

	    HARKAndroid::flac_mic_data fdata;

	    fdata.alloc(iter->port,
			csock_fd,
			pu,
			HARKAndroid::ControlObject::audio_info.sampling_rate,
			HARKAndroid::HARKControl->GetFlacParamBlocksize(),
			HARKAndroid::HARKControl->GetFlacParamMicMaxLpcOrder(),
			HARKAndroid::HARKControl->GetFlacParamMicRicePartitionOrder(),
			::progress_callback);

	    // fdata ほぞん
	    HARKAndroid::HARKControl->AddFLACMicDataSocket(fdata);

	  }
	  else{
	    LOGE("accept ERROR");
	    break;
	  }
	}
	else{
	  // timeout
	}
      }
    }

    FD_ZERO(&rfds);
    for(std::vector<HARKAndroid::port_fd_pair>::iterator iter = fds.begin(); iter != fds.end(); iter++) {
      FD_SET(iter->fd, &rfds);
    }
    
  }

exit_func:
  
  for(std::vector<HARKAndroid::port_fd_pair>::iterator iter = fds.begin(); iter != fds.end(); iter++) {
    close(iter->fd);
  }
  
  LOGD("flac server mainthread end");
  
  return(NULL);

}

static void *sloop(void *arg)
{

	std::auto_ptr<HARKAndroid::socks> p(reinterpret_cast<HARKAndroid::socks *>(arg));

	const int fd = p->fd;
	const int port = p->port;

#ifdef ENABLE_JNI_FOR_ANDROID
	JNIEnv *env = NULL;

	if(::g_vm->AttachCurrentThread(&env, NULL) != JNI_OK){
		LOGE("AttachCurrentThread Error");
		return(NULL);
	}
#endif // ENABLE_JNI_FOR_ANDROID
	{

		class cleanup {
		public:
			cleanup() {}
			~cleanup() {
#ifdef ENABLE_JNI_FOR_ANDROID
				if (::g_vm->DetachCurrentThread() != JNI_OK) {
					LOGE("DetachCurrentThread Error");
				}
#endif // ENABLE_JNI_FOR_ANDROID
			}
		};

		cleanup cleanup;

		// add socket
		HARKAndroid::HARKControl->AddSocket(port, fd);

		char buf[256];

		fd_set rfds;
		FD_ZERO(&rfds);
		FD_SET(fd, &rfds);

		while(1){
		
			int ret = ::select(fd + 1, &rfds, NULL, NULL, NULL);
			if(ret < 0){
				if(errno == EINTR){
					continue;
				}
				else{
					LOGE("select Error");
					break;
				}
			}
			else if(ret > 0){
				ssize_t read_count = ::recv(fd, buf, sizeof(buf), 0);
				if(read_count <= 0){
					break;
				}
			}
			else{
				// timeout
			}
			FD_ZERO(&rfds);
			FD_SET(fd, &rfds);
		}
		// remove socket
		HARKAndroid::HARKControl->RemoveSocket(port, fd);
		close(fd);
		LOGD("client thread end");
	}
	return(NULL);
}

static void *loop(void *arg)
{
	//// TAKIGAHIRA : for 64bit build support
	////const int port = reinterpret_cast<int>(arg);
	const long int l_port = reinterpret_cast<long int>(arg);
	const int port = (int)l_port;
#ifdef ENABLE_JNI_FOR_ANDROID
	JNIEnv *env = NULL;

	if(::g_vm->AttachCurrentThread(&env, NULL) != JNI_OK){
		LOGE("AttachCurrentThread Error");
		return(NULL);
	}
#endif // ENABLE_JNI_FOR_ANDROID
	{
		class cleanup {
		public:
			cleanup() {}
			~cleanup() {
#ifdef ENABLE_JNI_FOR_ANDROID
				if(::g_vm->DetachCurrentThread() != JNI_OK){
					LOGE("DetachCurrentThread Error");
				}
#endif // ENABLE_JNI_FOR_ANDROID
			}
		};

		cleanup cleanup;

		int fd = ::socket(AF_INET, SOCK_STREAM, 0);
		if(fd <= 0){
			LOGE("socket Error");
			return(NULL);
		}

		const int reuseaddr = 1;
		::setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr));

		struct sockaddr_in addr;
		addr.sin_family = AF_INET;
		addr.sin_addr.s_addr   = INADDR_ANY;
		addr.sin_port   = htons(port);

		if(::bind(fd, (struct sockaddr *) &addr, sizeof(addr)) != 0){
			::close(fd);
			LOGE("bind Error");
			return(NULL);
		}

		if(::listen(fd, 5) != 0){
			::close(fd);
			LOGE("listen Error");
			return(NULL);
		}
        
		HARKAndroid::HARKControl->AddServerSocket(port, fd);

        server_start_ok.Set();
        
		fd_set rfds;
		FD_ZERO(&rfds);
		FD_SET(fd, &rfds);

		while(1){
		
			int ret = ::select(fd + 1, &rfds, NULL, NULL, NULL);
			if(ret < 0){
				if(errno == EINTR){
					continue;
				}
				else{
					LOGE("select Error port %d", port);
					break;
				}
			}
			else if(ret > 0){
				struct sockaddr_in caddr;
				int client_len;
				//// TAKIGAHIRA : for native build
				////int csock_fd = ::accept(fd, (struct sockaddr *)&caddr, &client_len);
				int csock_fd = ::accept(fd, (struct sockaddr *)&caddr, (socklen_t*)&client_len);
				if(csock_fd != -1){
					HARKAndroid::socks *p = new HARKAndroid::socks(port, csock_fd);
					if(p != NULL){
                        
                        ::pthread_attr_t thread_attr;
                        ::pthread_attr_init(&thread_attr);
                        ::pthread_attr_setdetachstate(&thread_attr , PTHREAD_CREATE_DETACHED);
                        
						pthread_t thread0;
						int rr = ::pthread_create(&thread0, &thread_attr, ::sloop, reinterpret_cast<void *>(p));
                        ::pthread_attr_destroy(&thread_attr);
						if(rr != 0){
							delete p;
							LOGE("pthread_create ERROR");
							break;
						}
					}
					else{
						LOGE("new ERROR");
						break;
					}
				}
				else{
					LOGE("accept ERROR");
					break;
				}
			}
			else{
				// timeout
			}
			FD_ZERO(&rfds);
			FD_SET(fd, &rfds);
		}
		close(fd);
		LOGD("server mainthread %d end", port);
	}
	return(NULL);
}

#ifdef ENABLE_JNI_FOR_ANDROID
int _server_start(JNIEnv *env, jobject thiz)
#else
int _server_start()
#endif // ENABLE_JNI_FOR_ANDROID
{

    pthread_t thread0;
    ::pthread_attr_t thread_attr;
    ::pthread_attr_init(&thread_attr);
    ::pthread_attr_setdetachstate(&thread_attr , PTHREAD_CREATE_DETACHED);
    //void *ret;
    int rr = ::pthread_create(&thread0, &thread_attr, ::loop, reinterpret_cast<void *>(HA_PORT_A));
    if(rr != 0){
        LOGE("pthread_create ERROR");
    }

    server_start_ok.Wait();

    rr = ::pthread_create(&thread0, &thread_attr, ::loop, reinterpret_cast<void *>(HA_PORT_B));
    if(rr != 0){
        LOGE("pthread_create ERROR");
    }

    server_start_ok.Wait();

    rr = ::pthread_create(&thread0, &thread_attr, ::loop, reinterpret_cast<void *>(HA_PORT_C));
    if(rr != 0){
        LOGE("pthread_create ERROR");
    }

    server_start_ok.Wait();

    rr = ::pthread_create(&thread0, &thread_attr, ::loop, reinterpret_cast<void *>(HA_PORT_D));
    if(rr != 0){
        LOGE("pthread_create ERROR");
    }

    server_start_ok.Wait();

    rr = ::pthread_create(&thread0, &thread_attr, ::loop, reinterpret_cast<void *>(HA_PORT_E));
    if(rr != 0){
        LOGE("pthread_create ERROR");
    }

    server_start_ok.Wait();

    rr = ::pthread_create(&thread0, &thread_attr, ::loop, reinterpret_cast<void *>(HA_PORT_F)); // NAKAMURA
    if(rr != 0){
        LOGE("pthread_create ERROR");
    }

    server_start_ok.Wait();

    rr = ::pthread_create(&thread0, &thread_attr, ::loop, reinterpret_cast<void *>(HA_PORT_A_FLAC_HEADER));
    if(rr != 0){
        LOGE("pthread_create ERROR");
    }

    server_start_ok.Wait();

    rr = ::pthread_create(&thread0, &thread_attr, ::flac_mic_data_loop, NULL);
    if(rr != 0){
        LOGE("pthread_create ERROR");
    }

    server_start_ok.Wait();

    ::pthread_attr_destroy(&thread_attr);

    ::usleep(100000); // 0.1sec

    return(0);
}

#ifdef ENABLE_JNI_FOR_ANDROID
int _server_stop(JNIEnv *env, jobject thiz)
#else
int _server_stop()
#endif // ENABLE_JNI_FOR_ANDROID
{

    return(HARKAndroid::HARKControl->CloseServer());

}
