#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<pthread.h>
#define BUFF_SIZE 1024
#define ACCEPT 0
#define REJECT 1
#define DISCONNECT -1
int thread_num[25];	//스레드 번호 (해당 스레드 활성화시 번호 값 + 1, 비활성화시 0)
			//ex) thread_num[스레드 번호]==스레드 번호+1
int thread_id[25];	//스레드 ID 번호
			//ex) 스레드 아이디 = id[thread_id[스레드 번호]]
int client_socket_array[25];	//클라이언트 소캣, 각 스레드 마다 자신의 번호에 해당하는 소캣 사용
				//ex) 스레드가 사용 중인 소캣 == client_socket_array[스레드 번호]
char id[100][256];
char password[100][256];
char id_state[100];
int id_num;
char chat[100][BUFF_SIZE+5];
char chat_s[100], chat_e;
int stcmp(char* a,char *b)
{
	int i;
	for(i=0; a[i]&&b[i] ;i++)
	{
		if(a[i]!=b[i])
			return 1;
	}
	return 0;
}
int send_i(int client_socket,char* arry)//명령어 전송
{
	char buff_snd[BUFF_SIZE+5];
	sprintf(buff_snd,"i %s\n",arry);
	send(client_socket, buff_snd, strlen(buff_snd)+1,0);
}
int send_m(int client_socket,char arry[])//메세지 전송
{
	char buff_snd[BUFF_SIZE+5];
	sprintf(buff_snd,"m %s\n",arry);
	send(client_socket, buff_snd, strlen(buff_snd)+1,0);
}
int login(int t_num)
{
	int i;
	char buff_rcv[BUFF_SIZE+5];
	int client_socket;
	client_socket=client_socket_array[t_num-1];
	printf("%dth clinet try to login\n",t_num);
	while(1)
	{
		send_i(client_socket,"i");
		if(recv(client_socket, buff_rcv,BUFF_SIZE,0)<=0)
		{
			return DISCONNECT;
		}
		for(i=0;i<id_num;i++)
		{
			if(!stcmp(id[i],buff_rcv))
				break;
		}
		if(i==id_num)
		{
			send_m(client_socket,"ID is wrong");
			while(1)
			{
				if(recv(client_socket,buff_rcv,BUFF_SIZE,0)<=0)
					return DISCONNECT;
				if(buff_rcv[0]=='a')
					break;
			}
			continue;
		}
		else
		{
			if(id_state[i])
			{
				send_m(client_socket,"ID is being used.");
				while(1)
				{
					if(recv(client_socket,buff_rcv,BUFF_SIZE,0)<=0)
						return DISCONNECT;
					if(buff_rcv[0]=='a')
						break;
				}
				continue;
			}
		}
		send_i(client_socket,"p");//password 요구
		if(recv(client_socket, buff_rcv,BUFF_SIZE,0)<=0)
		{
			return DISCONNECT;
		}
		if(stcmp(password[i],buff_rcv))
		{
			send_m(client_socket, "password is wrong");
			while(1)
			{
				if(recv(client_socket,buff_rcv,BUFF_SIZE,0)<=0)
					return DISCONNECT;
				if(buff_rcv[0]=='a')
					break;
			}
			continue;
		}
		send_i(client_socket,"a");//접속 확인
		return i;
	}
}
int new_ID(int t_num)
{
	int i;
	char buff_rcv[BUFF_SIZE+5];
	int client_socket;
	client_socket=client_socket_array[t_num-1];
	printf("%dth clinet try to create ID\n",t_num);
	while(1)
	{
		send_i(client_socket,"i");
		if(recv(client_socket, buff_rcv,BUFF_SIZE,0)<=0)
		{
			return DISCONNECT;
		}
		for(i=0;i<id_num;i++)
		{
			if(!stcmp(id[i],buff_rcv))
				break;
		}
		if(i==id_num)
		{
			sprintf(id[id_num],"%s",buff_rcv);
			id_num++;
		}
		else
		{
			send_m(client_socket,"ID is already existing..");
			while(1)
			{
				if(recv(client_socket,buff_rcv,BUFF_SIZE,0)<=0)
					return DISCONNECT;
				if(buff_rcv[0]=='a')
					break;
			}
			continue;
		}
		send_i(client_socket,"p");//password 요구
		if(recv(client_socket, buff_rcv,BUFF_SIZE,0)<=0)
		{
			return DISCONNECT;
		}
		sprintf(password[i],"%s",buff_rcv);
		send_i(client_socket,"a");//접속 확인
		return i;
	}
}
void command(int t_num, char buff_rcv[])
{
	int i,j,k;
	char temp_buff[BUFF_SIZE+1];
	char buff_snd[BUFF_SIZE+1];
	sprintf(temp_buff,"./help\n");
	if(!stcmp(temp_buff,buff_rcv))
	{
		send_m(client_socket_array[t_num-1],"<Command list>");
		send_m(client_socket_array[t_num-1],"help : show list of command");
		send_m(client_socket_array[t_num-1],"list : show list of online user");
		send_m(client_socket_array[t_num-1],"message \"ID\" \"message\" : send message to ID");
		send_m(client_socket_array[t_num-1],"\n");
		return;
	}
	sprintf(temp_buff,"./list\n");
	if(!stcmp(temp_buff,buff_rcv))
	{
		send_m(client_socket_array[t_num-1],"<Online user list>");
		for(i=0;i<20;i++)
			if(thread_num[i] && thread_id[i]>=0)
			{
				send_m(client_socket_array[t_num-1],id[thread_id[i]]);
			}
		send_m(client_socket_array[t_num-1],"\n");
		return;
	}
	sprintf(temp_buff,"./message ");
	for(i=0;i<10;i++)
	{
		if(temp_buff[i]!=buff_rcv[i])
			break;
	}
	if(i==10)
	{
		for(i=0;i<id_num;i++)
		{
			for(j=0;buff_rcv[j+10]!=' ';j++)
			{
				if(id[i][j]!=buff_rcv[j+10])
					break;
			}
			if(buff_rcv[j+10]==' ')
			{
				if(id_state[i])
				{
					sprintf(temp_buff,"send to %s : %s", id[i], &buff_rcv[j+11] );
					send_m(client_socket_array[t_num-1],temp_buff);
					for(k=0;k<20;k++)
						if(thread_id[k]==i)
						{
							sprintf(temp_buff,"message from %s : %s",
								id[thread_num[t_num-1]],&buff_rcv[j+11]);
							send_m(client_socket_array[k],temp_buff);
							break;
						}
					break;
				}
				else
				{
					send_m(client_socket_array[t_num-1],"ID is offline");
					break;
				}
			}
		}
		if(i==id_num)
			send_m(client_socket_array[t_num-1],"ID is wrong");
	}
}
void message(int t_num, char buff_rcv[])
{
	int i;
	char buff_snd[BUFF_SIZE+1];
	sprintf(buff_snd,"%s : %s",id[thread_id[t_num-1]],buff_rcv);
	for(i=0;i<20;i++)
	{
		if(thread_num[i] && thread_id[i]>=0)
			send_m(client_socket_array[i],buff_snd);
	}
	sprintf(chat[chat_e],"%s",buff_snd);
	chat_e++;
	chat_e%=100;
	for(i=0;i<100;i++)
		if(id_state[i] || i>=id_num)
			chat_s[i]=chat_e;
}
void disconnect(int t_num, int i_num)
{
	printf("%dth client is disconnected\n",t_num);
	id_state[i_num]=0;
	thread_num[t_num-1]=0;
	close(client_socket_array[t_num-1]);
	client_socket_array[t_num-1]=0;
	thread_id[t_num-1]=0;
}
void* rutine(void* data)//data = &thread_num[스레드 번호]
{
	int t_num,i_num;//스레드 번호, 아이디 번호
	int client_socket;
	char buff_rcv[BUFF_SIZE+5],buff_snd[BUFF_SIZE+5];
	int rcv;
	int i,j;
	t_num=*((int*)data);
	//사용자가 이해하기 쉽도록 스레드 번호에 +1 값을 쓰도록 한다.
	client_socket=client_socket_array[t_num-1];
	//코드의 간결화를 위해 값을 복사한다.
	printf("%dth client is connected, socket_num is %d\n",t_num, client_socket);
	thread_id[t_num-1]=-1;
	while(1)
	{
		if(recv(client_socket,buff_rcv,BUFF_SIZE,0)<=0)
		{
				disconnect(t_num,i_num);
				return;
		}
		if(buff_rcv[0] == 'l')
		{
			send_i(client_socket,"a");
			while(1)
			{
				if(recv(client_socket,buff_rcv,BUFF_SIZE,0)<=0)
				{
						disconnect(t_num,i_num);
						return;
				}
				else
				{
					if(buff_rcv[0]=='a')
						break;
				}
			}
			i_num=login(t_num);
			break;
		}
		else
		{
			if(buff_rcv[0] == 'n')
			{
				send_i(client_socket,"a");
				while(1)
				{
					if(recv(client_socket,buff_rcv,BUFF_SIZE,0)<=0)
					{
							disconnect(t_num,i_num);
							return;
					}
					else
					{
						if(buff_rcv[0]=='a')
							break;
					}
				}
				i_num=new_ID(t_num);
				break;
			}
			else
			{
				send_i(client_socket,"c");
			}
		}
	}
	if(i_num==DISCONNECT)
	{
		disconnect(t_num,i_num);
		return;
	}
	thread_id[t_num-1]=i_num;
	id_state[i_num]=1;
	//로그인 도중 메세지가 전송되는 것을 방지, 클라이언트가 작업을 완료하는 것을 기다린다.
	while(recv(client_socket,buff_rcv,BUFF_SIZE,0))
	{
		if(buff_rcv[0]=='a')
			break;
	}
	sprintf(buff_snd,"log\n");
	send_m(client_socket,buff_snd);
	for(;chat_s[i_num]!=chat_e;chat_s[i_num]++)
	{
		sprintf(buff_snd,"%s\n",chat[chat_s[i_num]]);
		send_m(client_socket, buff_snd);
	}
	sprintf(buff_snd,"%s has joined",id[i_num]);
	for(i=0;i<20;i++)
	{
		if(thread_num[i] && thread_id[i]>=0)
			send_m(client_socket_array[i],buff_snd);
	}
	while(1)
	{
		rcv=recv(client_socket,buff_rcv,BUFF_SIZE,0);
		if(rcv>0)
		{
			printf("received\n");
			if(buff_rcv[0]=='.' && buff_rcv[1]=='/')
				command(t_num,buff_rcv);
			else
				message(t_num,buff_rcv);
		}
		else
		{
			break;
		}
	}
	sprintf(buff_snd,"%s has left",id[i_num]);
	for(i=0;i<20;i++)
	{
		if(thread_num[i])
			send_m(client_socket_array[i],buff_snd);
	}
	disconnect(t_num, i_num);
}
int main()
{
	int server_socket;
	int client_socket;
	pthread_t p_thread[25];
	struct sockaddr_in server_addr;
	struct sockaddr_in client_addr;
	int client_addr_size;
	int i;
	server_socket=socket(PF_INET, SOCK_STREAM, 0);
	if(server_socket==-1)
	{
		printf("socket error\n");
		exit(1);
	}
	memset(&server_addr,0,sizeof(server_addr));
	server_addr.sin_family	=AF_INET;
	server_addr.sin_port	=htons(4000);
	server_addr.sin_addr.s_addr=htonl(INADDR_ANY);
	if(-1==bind(server_socket,(struct sockaddr*)&server_addr,sizeof(server_addr)))
	{
		printf("bind error\n");
		exit(1);
	}
	printf("server started\n");
	for(i=0;i<20;i++)
		thread_num[i]=0;
	while(1)
	{
		if(-1==listen(server_socket,5))
		{
			printf("listen error\n");
			exit(1);
		}
		client_socket = accept(server_socket, (struct sockaddr*)&client_addr,&client_addr_size);
		if(client_socket==-1)
			continue;
		for(i=0;i<20;i++)
		{
			if(!thread_num[i])
			{
				client_addr_size= sizeof(client_addr);
				client_socket_array[i] = client_socket;
				thread_num[i]=i+1;
				memset(&p_thread[i],0,sizeof(p_thread[i]));
				pthread_create(&p_thread[i],NULL,rutine,(void *)&thread_num[i]);
				break;
			}
		}
		if(i==20)
			printf("error : Too many clients connected\n");
	}
	close(server_socket);
	return 0;
}