1 module Connection;
2 
3 import core.thread;
4 import gui;
5 import gdk.Threads : te = threadsEnter, tl = threadsLeave;
6 import gtk.Box;
7 import std.stdio;
8 import libdnet.dclient;
9 import std.socket;
10 import gtk.ListBox;
11 import gtk.Label;
12 
13 import areas.Channel;
14 import areas.MessageArea;
15 import areas.User;
16 import std.string;
17 
18 import core.sync.mutex;
19 
20 import gtk.Notebook;
21 
22 import std.conv;
23 
24 import gogga;
25 
26 
27 
28 public final class Connection : Thread
29 {
30     private GUI gui;
31     private Box box;
32     private ListBox channels;
33 
34     private DClient client;
35     private Address address;
36     private string[] auth;
37     private string statusText;
38 
39     /* TODO: Check if we need to protect */
40     /* TODO: So far usage is in signal handlers (mutex safved) and within te-tl lock for notifications */
41     private string currentChannel; /* TODO: Used to track what notificaitons come throug */
42     private Label currentChannelLabel; /* TODO: Using getChild would be nicer, but yeah, this is for the title */
43 
44     /**
45     * All joined Channel-s in this Connection 
46     */
47     private Notebook notebookSwitcher;
48     private MessageArea[] areas; /*TODO: Technically locking by GTK would make this not needed */
49     private Mutex chansLock;
50     private MessageArea focusedArea;
51 
52 
53     // public void setPrescence(string pres)
54     // {
55     //     /* The new status */
56     //     string newStatus = 
57     //     statusText = "";
58     //     statusText = 
59     // }
60 
61     this(GUI gui, Address address, string[] auth)
62     {
63         super(&worker);
64         this.gui = gui;
65         this.address = address;
66         this.auth = auth;
67 
68         /* Initialize locks */
69         initializeLocks();
70 
71         statusText = "Hey there, I'm using Dnet!";
72 
73         /* Start the notification atcher */
74         start();
75     }
76 
77     /**
78     * Initializes all locks (other than GDK)
79     */
80     private void initializeLocks()
81     {
82         chansLock =  new Mutex();
83     }
84 
85     private void worker()
86     {
87         /* Create a new Label */
88         currentChannelLabel = new Label("CHANNEL NAME GOES HERE");
89 
90         /**
91         * Setup the tab for this connection
92         */
93         te();
94         box = getChatPane();
95         gui.notebook.add(box);
96         //gui.notebook.setTabReorderable(box, true);
97         //gui.notebook    setChildPacking(box, true, true, 0, GtkPackType.START);
98        // gui.mainWindow.
99         gui.notebook.setTabLabelText(box, auth[0]~"@"~address.toString());
100         gui.notebook.showAll();
101         tl();
102 
103 
104         /**
105         * Connects and logs in
106         */
107         client = new DClient(address);
108         client.auth(auth[0], auth[1]); /* TODO: DO this without auth (the list in the loop, crahses server) */
109 
110         /* Set your prescense */
111         client.setProperty("pres","available");
112 
113 
114 
115         // te();
116         // import ProfileWIndow;
117         // ProfileWindow profile = new ProfileWindow(this, auth[0]);
118         // tl();
119 
120         /**
121         * Notification loop
122         *
123         * Awaits notifications and then displays them
124         */
125         while(true)
126         {
127             /* Receive a notification */
128             byte[] notificationData = client.awaitNotification();
129             gprintln("A new notification has arrived");
130             gprintln("Notification data: "~to!(string)(notificationData));
131 
132             te();
133             // import std.conv;
134             // textArea.add(new Label(to!(string)(notificationData)));
135             // textArea.showAll();
136             
137 
138             process(notificationData);
139             //gui.mainWindow.showAll();
140 
141             // import gtk.InfoBar;
142             // InfoBar notificationBar = new InfoBar();
143             // notificationBar.add(new Label("Poes"));
144             // notificationBar.setMessageType(GtkMessageType.INFO);
145             // notificationBar
146             
147             // box.add(notificationBar);
148             // notificationBar.showAll();
149  
150             notebookSwitcher.showAll();
151             
152 
153             tl();
154 
155             //Thread.sleep(dur!("seconds")(2));
156         }
157     }
158 
159 	/**
160 	* Processes an incoming notification
161 	* accordingly
162 	*/
163 	private void process(byte[] data)
164 	{
165 		/* Get the notification type */
166 		ubyte notificationType = data[0];
167         gprintln("NotificationType: "~to!(string)(notificationType));
168 
169 		/* For normal message (to channel or user) */
170 		if(notificationType == 0)
171 		{
172             /* TODO: Handle private messages */
173 
174             /* Decode is a test for assuming channel message received */
175             data = data[1..data.length];
176     
177             /* Decode the [usernameLength, username] */
178 		    ubyte usernameLength = data[1];
179             gprintln("NormalMessage: (Username length): "~to!(string)(usernameLength));
180             string username = cast(string)data[2..2+usernameLength];
181             gprintln("NormalMessage: (Username): "~username);
182 
183             /* Decode the [channelLength, channel] */
184 		    ubyte channelLength = data[2+usernameLength];
185             gprintln("NormalMessage: (Channel length): "~to!(string)(channelLength));
186             string channel = cast(string)data[2+usernameLength+1..2+usernameLength+1+channelLength];
187             gprintln("NormalMessage: (Channel): "~channel);
188         
189 
190             findChannel(channel).receiveMessage(username, cast(string)data[2+usernameLength+1+channelLength..data.length]);
191 		}
192 		/* Channel notification (ntype=1) */
193 		else if(notificationType == 1)
194 		{
195 			/* Get the sub-type */
196 			ubyte subType = data[1];
197 
198 			/* If the notification was leave (stype=0) */
199 			if(subType == 0)
200 			{
201                 /* LeaveInfo: <channel>,<username> */
202 				string[] leaveInfo = split(cast(string)data[2..data.length],",");
203                 writeln("LeaveInfo: ",leaveInfo);
204 
205                 /* Decode the LeaveInfo */
206                 string channel = leaveInfo[0];
207                 string username = leaveInfo[1];
208 
209                 /* Find the channel */
210                 Channel matchedChannel = findChannel(channel);
211 
212                 /* Channel leave */
213                 matchedChannel.channelLeave(username);
214 			}
215 			/* If the notification was join (stype=1) */
216 			else if(subType == 1)
217 			{
218                 /* JoinInfo: <channel>,<username> */
219 				string[] joinInfo = split(cast(string)data[2..data.length],",");
220                 writeln("JoinInfo: ",joinInfo);
221 
222                 /* Decode the JoinInfo */
223                 string channel = joinInfo[0];
224                 string username = joinInfo[1];
225 
226                 /* Find the channel */
227                 Channel matchedChannel = findChannel(channel);
228 
229                 /* Channel join */
230                 matchedChannel.channelJoin(username);
231 			}
232 			/* TODO: Unknown */
233 			else
234 			{
235 				
236 			}
237 		}
238 	}
239 
240 
241     private void addUserDM(User newUser)
242     {
243         /* TODO: However this we need to mutex for the areas as we could recieve a new message by watcher which adds for us */
244         chansLock.lock();
245         areas ~= newUser;
246         chansLock.unlock();
247     }
248 
249     /**
250     * Opens a new tab for a new direct message
251     *
252     * (To be called by a handler, which auto-mutexes)
253     *
254     * 1. Will add a new area
255     * 2. Will add a new tab to the notebook switcher
256     * 3. Will switch the current tab to said tab
257     */
258     public void addDirectMessage_unsafe(string username)
259     {
260         
261 
262         
263 
264         /* TODO: Get box over here etc. */
265 
266         gprintln("Henlo begi");
267 
268         /* Check if we have joined this user already */
269         User foundUser = findUser(username);
270 
271         gprintln("Henlo");
272 
273         /* If we have joined this user before */
274         if(foundUser)
275         {
276             /* TODO: Switch to */
277             writeln("nope time: "~username);
278 
279             
280         }
281         /* If we haven't joined this user before */
282         else
283         {
284             /* Create the new User area */
285             User newUser = new User(this, username);
286 
287             /* Add the user */
288             addUserDM(newUser);
289 
290             /* Set as the `foundChannel` */
291             foundUser = newUser;
292 
293             /* Get the Widgets container for this channel and add a tab for it */
294             notebookSwitcher.add(newUser.getBox());
295             notebookSwitcher.setTabReorderable(newUser.getBox(), true);
296             notebookSwitcher.setTabLabelText(newUser.getBox(), newUser.getUsername());
297 
298             writeln("hdsjghjsd");
299 
300             writeln("first time: "~username);
301 
302             // /* Get the user's list */
303             // newChannel.populateUsersList();
304         }
305 
306         /* Render recursively all children of the container and then the container itself */
307         box.showAll();
308 
309 
310 
311 
312 
313 
314     }
315 
316 
317     /**
318     * Attempts to find the User object you are looking for
319     */
320     public User findUser(string username)
321     {
322         User result;
323 
324         chansLock.lock();
325 
326         /**
327         * Loop through each MessageArea and only inspect those
328         * whose type is `Channel`
329         */
330         foreach(MessageArea area; areas)
331         {
332 
333             /* Make sure the object is of type `Channel` */
334             if(typeid(area) == typeid(User))
335             {
336                 /* Down-cast */
337                 User user = cast(User)area;
338 
339                 /* Find the matching channel */
340                 if(cmp(user.getUsername(), username) == 0)
341                 {
342                     result = user;
343                     break;
344                 }
345             }
346         }
347 
348         import std.stdio;
349         writeln("\""~username~"\"");
350 
351 
352         chansLock.unlock();
353 
354         return result;
355     }
356 
357 
358 
359     public void joinChannel(string channelName)
360     {
361         /* Check if we have joined this channel already */
362         Channel foundChannel = findChannel(channelName);
363 
364         /* If we have joined this channel before */
365         if(foundChannel)
366         {
367             /* TODO: Switch to */
368             writeln("nope time: "~channelName);
369 
370             
371         }
372         /* If we haven't joined this channel before */
373         else
374         {
375             /* Join the channel */
376             client.join(channelName);
377 
378             /* Create the Channel object */
379             Channel newChannel = new Channel(this, channelName);
380 
381             /* Add the channel */
382             addChannel(newChannel);
383 
384             /* Set as the `foundChannel` */
385             foundChannel = newChannel;
386 
387             /* Get the Widgets container for this channel and add a tab for it */
388             notebookSwitcher.add(newChannel.getBox());
389             notebookSwitcher.setTabReorderable(newChannel.getBox(), true);
390             notebookSwitcher.setTabLabelText(newChannel.getBox(), newChannel.getName());
391 
392             writeln("hdsjghjsd");
393 
394             writeln("first time: "~channelName);
395 
396             /* Get the user's list */
397             newChannel.populateUsersList();
398         }
399 
400         /* Render recursively all children of the container and then the container itself */
401         box.showAll();
402     }
403 
404 
405     private void channelList()
406     {
407         te();
408         channelList_unsafe();
409         tl();
410     }
411 
412     public DClient getClient()
413     {
414         return client;
415     }
416 
417     /**
418     * Lists all channels and displays them
419     *
420     * Only to be aclled when locked (i.e. by the event
421     * loop signal dispatch or when we lock it
422     * i.e. `channelList`)
423     */
424     private void channelList_unsafe()
425     {
426         string[] channelList = client.list();
427 
428         foreach(string channel; channelList)
429         {
430             channels.add(new Label(channel));
431             channels.showAll();
432         }
433     }
434     
435     /**
436     * Attempts to find the Channel object you are looking for
437     */
438     public Channel findChannel(string channelName)
439     {
440         Channel result;
441 
442         chansLock.lock();
443 
444         /**
445         * Loop through each MessageArea and only inspect those
446         * whose type is `Channel`
447         */
448         foreach(MessageArea area; areas)
449         {
450 
451             /* Make sure the object is of type `Channel` */
452             if(typeid(area) == typeid(Channel))
453             {
454                 /* Down-cast */
455                 Channel channel = cast(Channel)area;
456 
457                 /* Find the matching channel */
458                 if(cmp(channel.getName(), channelName) == 0)
459                 {
460                     result = channel;
461                     break;
462                 }
463             }
464         }
465 
466         import std.stdio;
467         writeln("\""~channelName~"\"");
468 
469 
470         chansLock.unlock();
471 
472         return result;
473     }
474 
475     /**
476     * Adds the given channel to the tarcking list
477     *
478     * This adds the Channel object to the list of
479     * channels joined
480     *
481     * TODO: Migrate the gui.d channel join selectChannel
482     * here
483     * NOTE: You must manually join it though
484     */
485     public void addChannel(Channel newChannel)
486     {
487         /* Add the channel to the `chans` tracking list */
488         chansLock.lock();
489         areas ~= newChannel;
490         chansLock.unlock();
491 
492         /* Add the channel to the channels list (sidebar) */
493         writeln("Adding channel "~newChannel.getName());
494         Label babaBooey = new Label(newChannel.getName()); /* TODO: Fuck Pango, fix here but yeah _ */
495         babaBooey.setUseMarkup(false);
496         babaBooey.setText(newChannel.getName());
497         channels.add(babaBooey);
498     }
499 
500     /**
501     * Called when you select a channel in the sidebar
502     *
503     * This moves you to the correct notebook tab for
504     * that channel
505     */
506     private void viewChannel(ListBox s)
507     {
508         /* Get the name of the channel selected */
509         string channelSelected = (cast(Label)(s.getSelectedRow().getChild())).getText();
510 
511         /* Check if we have joined this channel already */
512         Channel foundChannel = findChannel(channelSelected);
513         writeln(foundChannel is null);
514 
515         /* Switch to the channel's pane */
516         notebookSwitcher.setCurrentPage(foundChannel.getBox());
517 
518         box.showAll();
519     }
520 
521 
522     /**
523     * Creates a message box
524     *
525     * A message box consists of two labels
526     * one being the name of the person who sent
527     * the message and the next being the message
528     * itself
529     */
530     private Box createMessageBox()
531     {
532         return null;
533     }
534 
535     private Box getChatPane()
536     {
537         /* The main page of the tab */
538         Box box = new Box(GtkOrientation.HORIZONTAL, 1);
539 
540         /* The channels box */
541         Box channelBox = new Box(GtkOrientation.VERTICAL, 1);
542 
543         /* The channel's list */
544         channels = new ListBox();
545         channels.addOnSelectedRowsChanged(&viewChannel);
546 
547         channelBox.add(new Label("Channels"));
548         channelBox.add(channels);
549 
550         // /* The user's box */
551         // Box userBox = new Box(GtkOrientation.VERTICAL, 1);
552 
553         // /* The user's list */
554         // users = new ListBox();
555 
556         // userBox.add(new Label("Users"));
557         // userBox.add(users);
558         
559         // /* The text box */
560         // Box textBox = new Box(GtkOrientation.VERTICAL, 1);
561         // textBox.add(currentChannelLabel);
562         // textArea = new ListBox();
563         // import gtk.ScrolledWindow;
564 
565         // ScrolledWindow scrollTextChats = new ScrolledWindow(textArea);
566         // textBox.add(scrollTextChats);
567         // import gtk.TextView;
568         // textBox.add(new TextView());
569         
570 
571         // import gtk.TextView;
572         // TextView f = new TextView();
573         // textBox.add(f);
574         
575         notebookSwitcher = new Notebook();
576         notebookSwitcher.setScrollable(true);
577         //notebookSwitcher.add(newnew Label("test"));
578 
579         box.add(channelBox);
580         box.add(notebookSwitcher);
581         // box.add(textBox);
582         //box.packEnd(notebookSwitcher,0,0,0);
583 
584         // textBox.setChildPacking(scrollTextChats, true, true, 0, GtkPackType.START);
585         box.setChildPacking(notebookSwitcher, true, true, 0, GtkPackType.START);
586         
587         
588 
589         return box;
590     }
591 
592     private int getPageNum()
593     {
594         return gui.notebook.pageNum(box);
595     }
596 
597     public void shutdown()
598     {
599         /* This is called from gui.d */
600         int pageNum = getPageNum();
601 
602         if(pageNum == -1)
603         {
604             /* TODO: Error handling */
605         }
606         else
607         {
608             gui.notebook.removePage(pageNum);
609             gui.notebook.showAll();
610         }
611     }
612 }