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.client;
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             gprintln("Channel/User Notification: "~to!(string)(data));
177     
178             
179         
180             /* If this is a new message channel notification */
181             if(data[0] == 0)
182             {
183                 gprintln("New channel message received", DebugType.WARNING);
184 
185                 /* Decode the [usernameLength, username] */
186                 ubyte usernameLength = data[1];
187                 gprintln("ChannelMessage: (Username length): "~to!(string)(usernameLength));
188                 string username = cast(string)data[2..2+usernameLength];
189                 gprintln("ChannelMessage: (Username): "~username);
190 
191                 /* Decode the [channelLength, channel] */
192                 ubyte channelLength = data[2+usernameLength];
193                 gprintln("ChannelMessage: (Channel length): "~to!(string)(channelLength));
194                 string channel = cast(string)data[2+usernameLength+1..2+usernameLength+1+channelLength];
195                 gprintln("ChannelMessage: (Channel): "~channel);
196 
197                 findChannel(channel).receiveMessage(username, cast(string)data[2+usernameLength+1+channelLength..data.length]);
198             }
199             /* If this is a new direct message notification */
200             else if(data[0] == 1)
201             {
202                 gprintln("New direct message received", DebugType.WARNING);
203 
204                 /* Decode the [usernameLength, username] (username here is recipient's) */
205                 ubyte recipientLength = data[1];
206                 gprintln("DirectMessage: (Recipient length): "~to!(string)(recipientLength));
207                 string recipient = cast(string)data[2..2+recipientLength];
208                 gprintln("DirectMessage: (Recipient): "~recipient);
209 
210                 /* Decode the [usernameLength, username] (username here is sender's) */
211                 ubyte sendersLength = data[2+recipientLength];
212                 gprintln("DirectMessage: (Sender length): "~to!(string)(sendersLength));
213                 string sender = cast(string)data[2+recipientLength+1..2+recipientLength+1+sendersLength];
214                 gprintln("DirectMessage: (Sender): "~sender);
215 
216                 
217 
218                 /* The message is the remainder */
219                 string message = cast(string)data[2+recipientLength+1+sendersLength..data.length];
220                 gprintln("DirectMessage: (Message): "~message);
221 
222                 /**
223                 * TODO: DIfferes from channels, channels we never get delivered those we have no tab for as we haven't joined them
224                 * and because server side knows we haven't joined iot we don't receive the notifivcaiton, eher however, there is no
225                 * user tab possibly yet, so we will need to add it our selves */
226                 User userArea = findUser(sender);
227 
228                 if(userArea)
229                 {
230                     userArea.receiveMessage(sender, message);
231                 }
232                 else
233                 {
234                     /* Add a new UserArea which will generate a new tab for us */
235                     addDirectMessage_unsafe(sender);
236 
237                     /* The above statement adds an entry for us, now let's find the added UserArea */
238                     userArea = findUser(sender);
239 
240                     /* Now let's add the direct message */
241                     userArea.receiveMessage(sender, message);
242 
243                 }
244                 
245                 
246             }
247             else
248             {
249                 /* TODO: Handle this */
250                 gprintln("FOk");
251             }
252             
253 		}
254 		/* Channel notification (ntype=1) */
255 		else if(notificationType == 1)
256 		{
257 			/* Get the sub-type */
258 			ubyte subType = data[1];
259 
260 			/* If the notification was leave (stype=0) */
261 			if(subType == 0)
262 			{
263                 /* LeaveInfo: <channel>,<username> */
264 				string[] leaveInfo = split(cast(string)data[2..data.length],",");
265                 writeln("LeaveInfo: ",leaveInfo);
266 
267                 /* Decode the LeaveInfo */
268                 string channel = leaveInfo[0];
269                 string username = leaveInfo[1];
270 
271                 /* Find the channel */
272                 Channel matchedChannel = findChannel(channel);
273 
274                 /* Channel leave */
275                 matchedChannel.channelLeave(username);
276 			}
277 			/* If the notification was join (stype=1) */
278 			else if(subType == 1)
279 			{
280                 /* JoinInfo: <channel>,<username> */
281 				string[] joinInfo = split(cast(string)data[2..data.length],",");
282                 writeln("JoinInfo: ",joinInfo);
283 
284                 /* Decode the JoinInfo */
285                 string channel = joinInfo[0];
286                 string username = joinInfo[1];
287 
288                 /* Find the channel */
289                 Channel matchedChannel = findChannel(channel);
290 
291                 /* Channel join */
292                 matchedChannel.channelJoin(username);
293 			}
294 			/* TODO: Unknown */
295 			else
296 			{
297 				
298 			}
299 		}
300 	}
301 
302 
303     private void addUserDM(User newUser)
304     {
305         /* TODO: However this we need to mutex for the areas as we could recieve a new message by watcher which adds for us */
306         chansLock.lock();
307         areas ~= newUser;
308         chansLock.unlock();
309     }
310 
311     /**
312     * Opens a new tab for a new direct message
313     *
314     * (To be called by a handler, which auto-mutexes)
315     *
316     * 1. Will add a new area
317     * 2. Will add a new tab to the notebook switcher
318     * 3. Will switch the current tab to said tab
319     */
320     public void addDirectMessage_unsafe(string username)
321     {
322         
323 
324         
325 
326         /* TODO: Get box over here etc. */
327 
328         gprintln("Henlo begi");
329 
330         /* Check if we have joined this user already */
331         User foundUser = findUser(username);
332 
333         gprintln("Henlo");
334 
335         /* If we have joined this user before */
336         if(foundUser)
337         {
338             /* TODO: Switch to */
339             writeln("nope time: "~username);
340 
341             
342         }
343         /* If we haven't joined this user before */
344         else
345         {
346             /* Create the new User area */
347             User newUser = new User(this, username);
348 
349             /* Add the user */
350             addUserDM(newUser);
351 
352             /* Set as the `foundChannel` */
353             foundUser = newUser;
354 
355             /* Get the Widgets container for this channel and add a tab for it */
356             notebookSwitcher.add(newUser.getBox());
357             notebookSwitcher.setTabReorderable(newUser.getBox(), true);
358             notebookSwitcher.setTabLabelText(newUser.getBox(), newUser.getUsername());
359 
360             writeln("hdsjghjsd");
361 
362             writeln("first time: "~username);
363 
364             // /* Get the user's list */
365             // newChannel.populateUsersList();
366         }
367 
368         /* Render recursively all children of the container and then the container itself */
369         box.showAll();
370 
371 
372 
373 
374 
375 
376     }
377 
378 
379     /**
380     * Attempts to find the User object you are looking for
381     */
382     public User findUser(string username)
383     {
384         User result;
385 
386         chansLock.lock();
387 
388         /**
389         * Loop through each MessageArea and only inspect those
390         * whose type is `Channel`
391         */
392         foreach(MessageArea area; areas)
393         {
394 
395             /* Make sure the object is of type `Channel` */
396             if(typeid(area) == typeid(User))
397             {
398                 /* Down-cast */
399                 User user = cast(User)area;
400 
401                 /* Find the matching channel */
402                 if(cmp(user.getUsername(), username) == 0)
403                 {
404                     result = user;
405                     break;
406                 }
407             }
408         }
409 
410         import std.stdio;
411         writeln("\""~username~"\"");
412 
413 
414         chansLock.unlock();
415 
416         return result;
417     }
418 
419 
420 
421     public void joinChannel(string channelName)
422     {
423         /* Check if we have joined this channel already */
424         Channel foundChannel = findChannel(channelName);
425 
426         /* If we have joined this channel before */
427         if(foundChannel)
428         {
429             /* TODO: Switch to */
430             writeln("nope time: "~channelName);
431 
432             
433         }
434         /* If we haven't joined this channel before */
435         else
436         {
437             /* Join the channel */
438             client.join(channelName);
439 
440             /* Create the Channel object */
441             Channel newChannel = new Channel(this, channelName);
442 
443             /* Add the channel */
444             addChannel(newChannel);
445 
446             /* Set as the `foundChannel` */
447             foundChannel = newChannel;
448 
449             /* Get the Widgets container for this channel and add a tab for it */
450             notebookSwitcher.add(newChannel.getBox());
451             notebookSwitcher.setTabReorderable(newChannel.getBox(), true);
452             notebookSwitcher.setTabLabelText(newChannel.getBox(), newChannel.getName());
453 
454             writeln("hdsjghjsd");
455 
456             writeln("first time: "~channelName);
457 
458             /* Get the user's list */
459             newChannel.populateUsersList();
460         }
461 
462         /* Render recursively all children of the container and then the container itself */
463         box.showAll();
464     }
465 
466 
467     private void channelList()
468     {
469         te();
470         channelList_unsafe();
471         tl();
472     }
473 
474     public DClient getClient()
475     {
476         return client;
477     }
478 
479     /**
480     * Lists all channels and displays them
481     *
482     * Only to be aclled when locked (i.e. by the event
483     * loop signal dispatch or when we lock it
484     * i.e. `channelList`)
485     */
486     private void channelList_unsafe()
487     {
488         string[] channelList = client.list();
489 
490         foreach(string channel; channelList)
491         {
492             channels.add(new Label(channel));
493             channels.showAll();
494         }
495     }
496     
497     /**
498     * Attempts to find the Channel object you are looking for
499     */
500     public Channel findChannel(string channelName)
501     {
502         Channel result;
503 
504         chansLock.lock();
505 
506         /**
507         * Loop through each MessageArea and only inspect those
508         * whose type is `Channel`
509         */
510         foreach(MessageArea area; areas)
511         {
512 
513             /* Make sure the object is of type `Channel` */
514             if(typeid(area) == typeid(Channel))
515             {
516                 /* Down-cast */
517                 Channel channel = cast(Channel)area;
518 
519                 /* Find the matching channel */
520                 if(cmp(channel.getName(), channelName) == 0)
521                 {
522                     result = channel;
523                     break;
524                 }
525             }
526         }
527 
528         import std.stdio;
529         writeln("\""~channelName~"\"");
530 
531 
532         chansLock.unlock();
533 
534         return result;
535     }
536 
537     /**
538     * Adds the given channel to the tarcking list
539     *
540     * This adds the Channel object to the list of
541     * channels joined
542     *
543     * TODO: Migrate the gui.d channel join selectChannel
544     * here
545     * NOTE: You must manually join it though
546     */
547     public void addChannel(Channel newChannel)
548     {
549         /* Add the channel to the `chans` tracking list */
550         chansLock.lock();
551         areas ~= newChannel;
552         chansLock.unlock();
553 
554         /* Add the channel to the channels list (sidebar) */
555         writeln("Adding channel "~newChannel.getName());
556         Label babaBooey = new Label(newChannel.getName()); /* TODO: Fuck Pango, fix here but yeah _ */
557         babaBooey.setUseMarkup(false);
558         babaBooey.setText(newChannel.getName());
559         channels.add(babaBooey);
560     }
561 
562     /**
563     * Called when you select a channel in the sidebar
564     *
565     * This moves you to the correct notebook tab for
566     * that channel
567     */
568     private void viewChannel(ListBox s)
569     {
570         /* Get the name of the channel selected */
571         string channelSelected = (cast(Label)(s.getSelectedRow().getChild())).getText();
572 
573         /* Check if we have joined this channel already */
574         Channel foundChannel = findChannel(channelSelected);
575         writeln(foundChannel is null);
576 
577         /* Switch to the channel's pane */
578         notebookSwitcher.setCurrentPage(foundChannel.getBox());
579 
580         box.showAll();
581     }
582 
583 
584     /**
585     * Creates a message box
586     *
587     * A message box consists of two labels
588     * one being the name of the person who sent
589     * the message and the next being the message
590     * itself
591     */
592     private Box createMessageBox()
593     {
594         return null;
595     }
596 
597     private Box getChatPane()
598     {
599         /* The main page of the tab */
600         Box box = new Box(GtkOrientation.HORIZONTAL, 1);
601 
602         /* The channels box */
603         Box channelBox = new Box(GtkOrientation.VERTICAL, 1);
604 
605         /* The channel's list */
606         channels = new ListBox();
607         channels.addOnSelectedRowsChanged(&viewChannel);
608 
609         channelBox.add(new Label("Channels"));
610         channelBox.add(channels);
611 
612         // /* The user's box */
613         // Box userBox = new Box(GtkOrientation.VERTICAL, 1);
614 
615         // /* The user's list */
616         // users = new ListBox();
617 
618         // userBox.add(new Label("Users"));
619         // userBox.add(users);
620         
621         // /* The text box */
622         // Box textBox = new Box(GtkOrientation.VERTICAL, 1);
623         // textBox.add(currentChannelLabel);
624         // textArea = new ListBox();
625         // import gtk.ScrolledWindow;
626 
627         // ScrolledWindow scrollTextChats = new ScrolledWindow(textArea);
628         // textBox.add(scrollTextChats);
629         // import gtk.TextView;
630         // textBox.add(new TextView());
631         
632 
633         // import gtk.TextView;
634         // TextView f = new TextView();
635         // textBox.add(f);
636         
637         notebookSwitcher = new Notebook();
638         notebookSwitcher.setScrollable(true);
639         //notebookSwitcher.add(newnew Label("test"));
640 
641         box.add(channelBox);
642         box.add(notebookSwitcher);
643         // box.add(textBox);
644         //box.packEnd(notebookSwitcher,0,0,0);
645 
646         // textBox.setChildPacking(scrollTextChats, true, true, 0, GtkPackType.START);
647         box.setChildPacking(notebookSwitcher, true, true, 0, GtkPackType.START);
648         
649         
650 
651         return box;
652     }
653 
654     private int getPageNum()
655     {
656         return gui.notebook.pageNum(box);
657     }
658 
659     public void shutdown()
660     {
661         /* This is called from gui.d */
662         int pageNum = getPageNum();
663 
664         if(pageNum == -1)
665         {
666             /* TODO: Error handling */
667         }
668         else
669         {
670             gui.notebook.removePage(pageNum);
671             gui.notebook.showAll();
672         }
673     }
674 }