#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include <error.h>
#include <netdb.h>

#include <libraries/mui.h>
#include <libraries/asl.h>
#include <libraries/gadtools.h>
#include <libraries/multiuser.h>
#include <libraries/asl.h>
#include <libraries/iffparse.h>
#include <libraries/multiuser.h>

#include <proto/dos.h>
#include <proto/exec.h>
#include <proto/intuition.h>
#include <proto/muimaster.h>
#include <proto/asl.h>
#include <proto/multiuser.h>

#define StringSucc(win,fromg,tog)\
	DoMethod(fromg,MUIM_Notify,MUIA_String_Acknowledge,MUIV_EveryTime,\
	win,3,MUIM_Set,MUIA_Window_ActiveObject,tog)
#define SetButtonID(app,butobj,id)\
	DoMethod(butobj,MUIM_Notify,MUIA_Pressed,FALSE,\
		app,2,MUIM_Application_ReturnID,id)
#define GetString(strobj,tostr,tempptr)\
	{ get(strobj,MUIA_String_Contents,&tempptr);\
	  strcpy(tostr,tempptr); }
#define Text TextObject,TextFrame,End

char VersionTag[]="\0$VER: JoranAmimsgWin 0.1 (14.12.93)";

struct Library *MUIMasterBase;
struct muBase *muBase;

struct AmiMsg {
	struct MinNode am_Node;
	char *am_user;
	char *am_host;
	char *am_msg;
	struct sockaddr_in am_addr;
	time_t am_time;
};

struct AmiMsgMsg {
	struct Message amm_Msg;
	struct AmiMsg amm_AmiMsg;
};

#define PORTNAME "JoranAmiMsgWin"
	
struct MinList MsgList;
struct MsgPort *MyPort;
ULONG MyPortSig;
struct AmiMsg *InfoAmiMsg;

enum {
	ShowInfo_ID=1,
	InfoReply_ID,
	MainReply_ID,
	MainSend_ID,
	SendClose_ID,
	SendHostAck_ID,
	SendSend_ID,
	ClearList_ID,
	SendUser_ID
};

char buf[1024];
char hostname[128];

APTR App;
APTR MainWin;
APTR AmiMsgList,AmiMsgListview,InfoBut,InfoIp;
APTR MainQuit,ClearList;
APTR SendBut,ReplyBut;
APTR InfoWin;
APTR InfoName,InfoMsg,InfoOkBut,InfoTime;
APTR InfoReplyBut;
APTR SendWin;
APTR SendHost,SendIp,SendMsg;
APTR SendCancel,SendSend,SendFrom;

long gethostname(char *,long len);
ULONG __asm __saveds MsgMultiHookFunc(register __a1 struct AmiMsg *,register __a2 char **);
struct Hook MsgMultiHook={0,0,MsgMultiHookFunc,0,0};

ULONG __asm __saveds MsgMultiHookFunc(register __a1 struct AmiMsg *am,register __a2 char **cols)
{
	cols[0]=am->am_user;
	cols[1]="@";
	cols[2]=am->am_host;
	cols[3]=am->am_msg;

	return 0;
}

void FreeAmiMsg(struct AmiMsg *Msg)
{
	if(!Msg) return;
	if(Msg->am_user) FreeMem(Msg->am_user,strlen(Msg->am_user)+1);
	if(Msg->am_host) FreeMem(Msg->am_host,strlen(Msg->am_host)+1);
	if(Msg->am_msg) FreeMem(Msg->am_msg,strlen(Msg->am_msg)+1);
	FreeMem(Msg,sizeof(struct AmiMsg));
}

struct AmiMsg *CopyFromAmiMsgMsg(struct AmiMsgMsg *Msg)
{
	struct AmiMsg *am;
	
	if(am=AllocMem(sizeof(struct AmiMsg),MEMF_ANY|MEMF_CLEAR))
	{
		if(am->am_user=AllocMem(strlen(Msg->amm_AmiMsg.am_user)+1,MEMF_ANY))
		{
			strcpy(am->am_user,Msg->amm_AmiMsg.am_user);
                  			
			if(am->am_host=AllocMem(strlen(Msg->amm_AmiMsg.am_host)+1,MEMF_ANY))
			{
				strcpy(am->am_host,Msg->amm_AmiMsg.am_host);
			
				if(am->am_msg=AllocMem(strlen(Msg->amm_AmiMsg.am_msg)+1,MEMF_ANY))
				{
					strcpy(am->am_msg,Msg->amm_AmiMsg.am_msg);
					
					memcpy(&am->am_addr,&Msg->amm_AmiMsg.am_addr,sizeof(struct sockaddr_in));
					am->am_time=Msg->amm_AmiMsg.am_time;
					
					return am;
				}
			}
		}
	}
	
	FreeAmiMsg(am);
	
	return NULL;
}

char *InitAll(void)
{
	struct MsgPort *OtherPort;
	
	NewList((struct List *)&MsgList);
	
	Forbid();
	OtherPort=FindPort(PORTNAME);
	if(!OtherPort) MyPort=CreatePort(PORTNAME,0);
	Permit();

	if(OtherPort) return "Program is already running.";
	if(!MyPort) return "Error creating port";
	
	MyPortSig=1<<MyPort->mp_SigBit;
	
	return NULL;
}

void CloseAll(void)
{
	struct AmiMsg *am;
	
	if(MyPort)
	{
		Forbid();
		while(GetMsg(MyPort));
		DeletePort(MyPort);
		Permit();

		while(!IsListEmpty((struct List *)&MsgList))
		{
			am=(struct AmiMsg *)MsgList.mlh_Head;
			Remove((struct Node *)am);
			FreeAmiMsg(am);
		}
	}
	if(MUIMasterBase) CloseLibrary((struct Library *)MUIMasterBase);
	if(muBase) CloseLibrary((struct Library *)muBase);
}

struct AmiMsg *GetSelAmiMsg(void)
{
	ULONG pos;
	struct AmiMsg *am;
	
	get(AmiMsgList,MUIA_List_Active,&pos);
	if(pos==MUIV_List_Active_Off) return NULL;
	
	for(am=(struct AmiMsg *)MsgList.mlh_Head;am!=(struct AmiMsg *)&MsgList.mlh_Tail;am=(struct AmiMsg *)am->am_Node.mln_Succ)
		if(!(pos--)) break;
	
	return am;
}

void DoShowInfo(void)
{
	struct AmiMsg *am;
	
	if(!(am=GetSelAmiMsg())) return;
	InfoAmiMsg=am;
		
	strcpy(buf,am->am_user);strcat(buf,"@");strcat(buf,am->am_host);
	set(InfoName,MUIA_Text_Contents,buf);
	set(InfoIp,MUIA_Text_Contents,inet_ntoa(am->am_addr.sin_addr));
	strcpy(buf,ctime(&am->am_time));*strchr(buf,'\n')='\0';
	set(InfoTime,MUIA_Text_Contents,buf);
	set(InfoMsg,MUIA_Floattext_Text,am->am_msg);
	
	set(InfoWin,MUIA_Window_Open,TRUE);
	
}

void errorreq(char *fmt)
{
	MUI_Request(App,MainWin,0,"JoranAmiMsgWin","Hmmm",
		fmt
	);
}

void perrorreq(char *fmt)
{
	MUI_Request(App,MainWin,0,"JoranAmiMsgWin","Hmmm",
		"%s: %s",fmt,strerror(errno)
	);
}

void DoSend(struct AmiMsg *am)
{
	BOOL running=TRUE;
	struct sockaddr_in server;
	struct hostent *hp=NULL;
	struct muUserInfo *UserInfo;
	
	set(App,MUIA_Application_Sleep,TRUE);
	
	if(muBase)
	{
		if(UserInfo=muAllocUserInfo())
		{
			UserInfo->uid=(muGetTaskOwner(NULL)&muMASK_UID)>>16;
			if(muGetUserInfo(UserInfo,muKeyType_uid))
				set(SendFrom,MUIA_Text_Contents,UserInfo->UserID);
			else
				set(SendFrom,MUIA_Text_Contents,"unknown");
			muFreeUserInfo(UserInfo);
		} else set(SendFrom,MUIA_Text_Contents,"unknown");
	}
	
	if(am)
	{
		set(SendHost,MUIA_String_Contents,am->am_host);
		set(SendIp,MUIA_Text_Contents,inet_ntoa(am->am_addr.sin_addr));
		set(SendMsg,MUIA_String_Contents,"");
	}
	else
	{
		set(SendHost,MUIA_String_Contents,"");
		set(SendIp,MUIA_Text_Contents,"");
		set(SendMsg,MUIA_String_Contents,"");	
	}
	set(SendWin,MUIA_Window_Open,TRUE);
	if(!muBase) set(SendWin,MUIA_Window_ActiveObject,SendFrom);
	else
	{
		if(am) set(SendWin,MUIA_Window_ActiveObject,SendMsg);
		else set(SendWin,MUIA_Window_ActiveObject,SendHost);
	}
	
	while(running)
	{
		ULONG sigs;
		
		switch(DoMethod(App,MUIM_Application_Input,&sigs))
		{
			char *host;
			struct in_addr ia;
			struct servent *sp;
			char *msg,*from;
			int s;
			
			case MUIV_Application_ReturnID_Quit:
				running=FALSE;
				DoMethod(App,MUIM_Application_ReturnID,MUIV_Application_ReturnID_Quit);
				break;
			case SendClose_ID:
				running=FALSE;
				break;
			case SendHostAck_ID:
				get(SendHost,MUIA_String_Contents,&host);
				if(!*host) hp=gethostbyname("localhost");
				else hp=gethostbyname(host);
				if(hp)
				{
					memcpy((char *)&ia,hp->h_addr,hp->h_length);
					set(SendIp,MUIA_Text_Contents,inet_ntoa(ia));
					set(SendWin,MUIA_Window_ActiveObject,SendMsg);
				}
				else
				{
					set(SendIp,MUIA_Text_Contents,"unknown host..");
					set(SendWin,MUIA_Window_ActiveObject,SendHost);
				}
				break;
			case SendUser_ID:
				if(am) set(SendWin,MUIA_Window_ActiveObject,SendMsg);
				else set(SendWin,MUIA_Window_ActiveObject,SendHost);
				break;
			case SendSend_ID:
				gethostname(hostname,sizeof(hostname));
				sp=getservbyname("amimsg","tcp");
				if(sp==NULL)
					errorreq("unknown service");
				else
				{
					if(!hp && !am)
					{
						errorreq("unknown host");
						break;
					}

					bzero((char *)&server,sizeof(server));
					if(hp)
					{
						bcopy(hp->h_addr,(char *)&server.sin_addr,hp->h_length);
						server.sin_family=hp->h_addrtype;
					}
					else bcopy(&am->am_addr,&server,sizeof(server));
					server.sin_port=sp->s_port;
					
					s=socket(AF_INET,SOCK_STREAM,0);
					if(s<0)
						perrorreq("socket");
					else
					{
						set(SendWin,MUIA_Window_Sleep,TRUE);
						if(connect(s,(struct sockaddr *)&server,sizeof(server))<0)
							perrorreq("connect");
						else
						{
							get(SendMsg,MUIA_String_Contents,&msg);
							if(muBase) get(SendFrom,MUIA_Text_Contents,&from);
							else get(SendFrom,MUIA_String_Contents,&from);
							if(!*from) from="unknown";
							sprintf(buf,
								"HOST %s\n"
								"USER %s\n"
								"MSG %s\n",
								hostname,
								from,
								msg
							);
							send(s,buf,strlen(buf),0);
						}
						set(SendWin,MUIA_Window_Sleep,FALSE);
						CloseSocket(s);
					}
				}
				running=FALSE;
				break;
		}
		if (running && sigs) Wait(sigs);
	}
	
	set(SendWin,MUIA_Window_Open,FALSE);
	set(App,MUIA_Application_Sleep,FALSE);
}

void DoClearList(void)
{
	struct AmiMsg *am;
	
	set(InfoWin,MUIA_Window_Open,FALSE);
	DoMethod(AmiMsgList,MUIM_List_Clear);
	while(!IsListEmpty((struct List *)&MsgList))
	{
		am=(struct AmiMsg *)MsgList.mlh_Head;
		Remove((struct Node *)am);
		FreeAmiMsg(am);
	}
}

void main(int argc)
{
	BOOL running=TRUE;
	char *errmsg;
	
	atexit(CloseAll);
	if(!(MUIMasterBase=OpenLibrary(MUIMASTER_NAME,MUIMASTER_VMIN))) exit(20);
	muBase=(struct muBase *)OpenLibrary(MULTIUSERNAME,MULTIUSERVERSION);
		
	App = ApplicationObject,
		MUIA_Application_Title,		"AmiMsgWin",
		MUIA_Application_Version,	"$VER: JoranAmiMsgWin 0.1 (16.12.93)",
		MUIA_Application_Copyright,"©1993 by Joran Jessurun",
		MUIA_Application_Author,	"Joran Jessurun",
		MUIA_Application_Base,		"AMIMSGWIN",
		MUIA_Application_Description,"GUI for JoranAmiMsg",
		SubWindow, MainWin=WindowObject,
			MUIA_Window_Title,		"AmiMsgWin V0.1",
			MUIA_Window_ID,			MAKE_ID('M','A','I','N'),
			WindowContents,
			VGroup, 
				Child, AmiMsgListview=ListviewObject,
					MUIA_Listview_List, AmiMsgList=ListObject,
						ReadListFrame,
						MUIA_List_Format,"DELTA=0,DELTA=0,DELTA=10,",
						MUIA_List_DisplayHook,&MsgMultiHook,
					End,
				End,
				Child, HGroup,
					MUIA_Group_SameWidth,TRUE,
					Child, SendBut=SimpleButton("Send"),
					Child, ReplyBut=SimpleButton("Reply"),
					Child, InfoBut=SimpleButton("Info"),
					Child, ClearList=SimpleButton("Clear List"),
					Child, MainQuit=SimpleButton("Quit"),
				End,
			End,
		End,
		SubWindow, InfoWin=WindowObject,
			MUIA_Window_Title,		"Message Info",
			MUIA_Window_ID,			MAKE_ID('M','S','G','I'),
			WindowContents,
			VGroup,
   			Child, HGroup,
   				MUIA_Group_Columns,2,
   				Child, Label1("name:"),
   				Child, InfoName=Text,
   				Child, Label1("ip:"),
   				Child, InfoIp=Text,
   				Child, Label1("time:"),
   				Child, InfoTime=Text,
   			End,
				Child, HGroup,
					Child, VGroup,
						Child, Label1("message:"),
						Child, VSpace(0),
					End,
					Child,InfoMsg=FloattextObject,
						TextFrame,
					End,
				End,
				Child, HGroup,
					MUIA_Group_SameWidth,TRUE,
					Child, InfoOkBut=SimpleButton("Ok"),
					Child, HSpace(0),
					Child, InfoReplyBut=SimpleButton("Reply"),
				End,
			End,
		End,
		SubWindow, SendWin=WindowObject,
			MUIA_Window_Title,		"Send Message",
			MUIA_Window_ID,			MAKE_ID('S','M','S','G'),
			WindowContents,
			VGroup,
				Child, HGroup,
					MUIA_Group_Columns,2,
					Child, Label2("from:"),
					Child, SendFrom=muBase?Text:String("",20),
					Child, Label2("host:"),
					Child, SendHost=String("",255),
					Child, Label1("ip:"),
					Child, SendIp=Text,
					Child, Label2("msg:"),
					Child, SendMsg=String("",255),
				End,
				Child, HGroup,
					MUIA_Group_SameWidth,TRUE,
					Child, SendSend=SimpleButton("Send"),
					Child, HSpace(0),
					Child, SendCancel=SimpleButton("Cancel"),
				End,
			End,
		End,
	End;

	if(!App)
	{
		MUI_Request(NULL,NULL,0,"AmiMsgWin V0.1","Hmmm",
			"Error initialising program."
		);
		exit(20);
	}
	
	// Install notify's to main program
	DoMethod(MainWin,MUIM_Notify,MUIA_Window_CloseRequest,TRUE,
		App,2,MUIM_Application_ReturnID,MUIV_Application_ReturnID_Quit);
	SetButtonID(App,InfoBut,ShowInfo_ID);
	DoMethod(AmiMsgListview,MUIM_Notify,MUIA_Listview_DoubleClick,TRUE,
		App,2,MUIM_Application_ReturnID,ShowInfo_ID);
	DoMethod(InfoWin,MUIM_Notify,MUIA_Window_CloseRequest,TRUE,
		InfoWin,3,MUIM_Set,MUIA_Window_Open,FALSE);
	DoMethod(InfoOkBut,MUIM_Notify,MUIA_Pressed,FALSE,
		InfoWin,3,MUIM_Set,MUIA_Window_Open,FALSE);
	SetButtonID(App,ReplyBut,MainReply_ID);
	SetButtonID(App,SendBut,MainSend_ID);
	SetButtonID(App,InfoReplyBut,InfoReply_ID);
	DoMethod(SendWin,MUIM_Notify,MUIA_Window_CloseRequest,TRUE,
		App,2,MUIM_Application_ReturnID,SendClose_ID);
	DoMethod(SendHost,MUIM_Notify,MUIA_String_Acknowledge,MUIV_EveryTime,
		App,2,MUIM_Application_ReturnID,SendHostAck_ID);
	SetButtonID(App,SendSend,SendSend_ID);
	SetButtonID(App,SendCancel,SendClose_ID);
	DoMethod(SendMsg,MUIM_Notify,MUIA_String_Acknowledge,MUIV_EveryTime,
		App,2,MUIM_Application_ReturnID,SendSend_ID);
	SetButtonID(App,MainQuit,MUIV_Application_ReturnID_Quit);
	SetButtonID(App,ClearList,ClearList_ID);
	if(!muBase)
	{
		DoMethod(SendFrom,MUIM_Notify,MUIA_String_Acknowledge,MUIV_EveryTime,
			App,2,MUIM_Application_ReturnID,SendUser_ID);
	}
	DoMethod(MainWin,MUIM_Window_SetCycleChain,
		AmiMsgListview,SendBut,ReplyBut,InfoBut,
		ClearList,MainQuit,
		NULL);
	DoMethod(InfoWin,MUIM_Window_SetCycleChain,
		InfoOkBut,InfoReplyBut,
		NULL);
	DoMethod(SendWin,MUIM_Window_SetCycleChain,
		SendFrom,SendHost,SendMsg,
		SendSend,SendCancel,
		NULL);

	set(MainWin,MUIA_Window_Open,TRUE);

	if(errmsg=InitAll())
	{
		MUI_Request(App,MainWin,0,"Error initialising AmiMsgWin","Hmmm",
			errmsg
		);
		running=FALSE;
	}

	while (running)
	{
	   ULONG signals;
		struct AmiMsgMsg *amm;

		while(amm=(struct AmiMsgMsg *)GetMsg(MyPort))
		{
			struct AmiMsg *am;
			
			am=CopyFromAmiMsgMsg(amm);
			ReplyMsg((struct Message *)amm);
			if(am)
			{
				LONG entries;
				AddTail((struct List *)&MsgList,(struct Node *)am);
				DoMethod(AmiMsgList,MUIM_List_Insert,&am,1,MUIV_List_Insert_Bottom);
				get(AmiMsgList,MUIA_List_Entries,&entries);
				DoMethod(AmiMsgList,MUIM_List_Jump,entries-1);
			}
		}

	   switch (DoMethod(App,MUIM_Application_Input,&signals))
	   {
			struct AmiMsg *am;
			
			case MUIV_Application_ReturnID_Quit:
				running = FALSE;
				break;
			case ShowInfo_ID:
				DoShowInfo();
				break;
			case InfoReply_ID:
				DoSend(InfoAmiMsg);
				break;
			case MainReply_ID:
				am=GetSelAmiMsg();
				if(am) DoSend(am);
				break;
			case MainSend_ID:
				DoSend(NULL);	
				break;
			case ClearList_ID:
				DoClearList();
				break;
	   }

	   if (running && signals) Wait(signals|MyPortSig);
	}
	
	if(App) DisposeObject(App);
	
	exit(0);
}

