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             gprintln(newUser.getBox());
294 
295             /* Get the Widgets container for this channel and add a tab for it */
296             notebookSwitcher.add(newUser.getBox());
297             notebookSwitcher.setTabReorderable(newUser.getBox(), true);
298             notebookSwitcher.setTabLabelText(newUser.getBox(), newUser.getUsername());
299 
300             writeln("hdsjghjsd");
301 
302             writeln("first time: "~username);
303 
304             // /* Get the user's list */
305             // newChannel.populateUsersList();
306         }
307 
308         /* Render recursively all children of the container and then the container itself */
309         box.showAll();
310 
311 
312 
313 
314 
315 
316     }
317 
318 
319     /**
320     * Attempts to find the User object you are looking for
321     */
322     public User findUser(string username)
323     {
324         User result;
325 
326         chansLock.lock();
327 
328         /**
329         * Loop through each MessageArea and only inspect those
330         * whose type is `Channel`
331         */
332         foreach(MessageArea area; areas)
333         {
334 
335             /* Make sure the object is of type `Channel` */
336             if(typeid(area) == typeid(User))
337             {
338                 /* Down-cast */
339                 User user = cast(User)area;
340 
341                 /* Find the matching channel */
342                 if(cmp(user.getUsername(), username) == 0)
343                 {
344                     result = user;
345                     break;
346                 }
347             }
348         }
349 
350         import std.stdio;
351         writeln("\""~username~"\"");
352 
353 
354         chansLock.unlock();
355 
356         return result;
357     }
358 
359 
360 
361     public void joinChannel(string channelName)
362     {
363         /* Check if we have joined this channel already */
364         Channel foundChannel = findChannel(channelName);
365 
366         /* If we have joined this channel before */
367         if(foundChannel)
368         {
369             /* TODO: Switch to */
370             writeln("nope time: "~channelName);
371 
372             
373         }
374         /* If we haven't joined this channel before */
375         else
376         {
377             /* Join the channel */
378             client.join(channelName);
379 
380             /* Create the Channel object */
381             Channel newChannel = new Channel(this, channelName);
382 
383             /* Add the channel */
384             addChannel(newChannel);
385 
386             /* Set as the `foundChannel` */
387             foundChannel = newChannel;
388 
389             /* Get the Widgets container for this channel and add a tab for it */
390             notebookSwitcher.add(newChannel.getBox());
391             notebookSwitcher.setTabReorderable(newChannel.getBox(), true);
392             notebookSwitcher.setTabLabelText(newChannel.getBox(), newChannel.getName());
393 
394             writeln("hdsjghjsd");
395 
396             writeln("first time: "~channelName);
397 
398             /* Get the user's list */
399             newChannel.populateUsersList();
400         }
401 
402         /* Render recursively all children of the container and then the container itself */
403         box.showAll();
404     }
405 
406 
407     private void channelList()
408     {
409         te();
410         channelList_unsafe();
411         tl();
412     }
413 
414     public DClient getClient()
415     {
416         return client;
417     }
418 
419     /**
420     * Lists all channels and displays them
421     *
422     * Only to be aclled when locked (i.e. by the event
423     * loop signal dispatch or when we lock it
424     * i.e. `channelList`)
425     */
426     private void channelList_unsafe()
427     {
428         string[] channelList = client.list();
429 
430         foreach(string channel; channelList)
431         {
432             channels.add(new Label(channel));
433             channels.showAll();
434         }
435     }
436     
437     /**
438     * Attempts to find the Channel object you are looking for
439     */
440     public Channel findChannel(string channelName)
441     {
442         Channel result;
443 
444         chansLock.lock();
445 
446         /**
447         * Loop through each MessageArea and only inspect those
448         * whose type is `Channel`
449         */
450         foreach(MessageArea area; areas)
451         {
452 
453             /* Make sure the object is of type `Channel` */
454             if(typeid(area) == typeid(Channel))
455             {
456                 /* Down-cast */
457                 Channel channel = cast(Channel)area;
458 
459                 /* Find the matching channel */
460                 if(cmp(channel.getName(), channelName) == 0)
461                 {
462                     result = channel;
463                     break;
464                 }
465             }
466         }
467 
468         import std.stdio;
469         writeln("\""~channelName~"\"");
470 
471 
472         chansLock.unlock();
473 
474         return result;
475     }
476 
477     /**
478     * Adds the given channel to the tarcking list
479     *
480     * This adds the Channel object to the list of
481     * channels joined
482     *
483     * TODO: Migrate the gui.d channel join selectChannel
484     * here
485     * NOTE: You must manually join it though
486     */
487     public void addChannel(Channel newChannel)
488     {
489         /* Add the channel to the `chans` tracking list */
490         chansLock.lock();
491         areas ~= newChannel;
492         chansLock.unlock();
493 
494         /* Add the channel to the channels list (sidebar) */
495         writeln("Adding channel "~newChannel.getName());
496         Label babaBooey = new Label(newChannel.getName()); /* TODO: Fuck Pango, fix here but yeah _ */
497         babaBooey.setUseMarkup(false);
498         babaBooey.setText(newChannel.getName());
499         channels.add(babaBooey);
500     }
501 
502     /**
503     * Called when you select a channel in the sidebar
504     *
505     * This moves you to the correct notebook tab for
506     * that channel
507     */
508     private void viewChannel(ListBox s)
509     {
510         /* Get the name of the channel selected */
511         string channelSelected = (cast(Label)(s.getSelectedRow().getChild())).getText();
512 
513         /* Check if we have joined this channel already */
514         Channel foundChannel = findChannel(channelSelected);
515         writeln(foundChannel is null);
516 
517         /* Switch to the channel's pane */
518         notebookSwitcher.setCurrentPage(foundChannel.getBox());
519 
520         box.showAll();
521     }
522 
523 
524     /**
525     * Creates a message box
526     *
527     * A message box consists of two labels
528     * one being the name of the person who sent
529     * the message and the next being the message
530     * itself
531     */
532     private Box createMessageBox()
533     {
534         return null;
535     }
536 
537     private Box getChatPane()
538     {
539         /* The main page of the tab */
540         Box box = new Box(GtkOrientation.HORIZONTAL, 1);
541 
542         /* The channels box */
543         Box channelBox = new Box(GtkOrientation.VERTICAL, 1);
544 
545         /* The channel's list */
546         channels = new ListBox();
547         channels.addOnSelectedRowsChanged(&viewChannel);
548 
549         channelBox.add(new Label("Channels"));
550         channelBox.add(channels);
551 
552         // /* The user's box */
553         // Box userBox = new Box(GtkOrientation.VERTICAL, 1);
554 
555         // /* The user's list */
556         // users = new ListBox();
557 
558         // userBox.add(new Label("Users"));
559         // userBox.add(users);
560         
561         // /* The text box */
562         // Box textBox = new Box(GtkOrientation.VERTICAL, 1);
563         // textBox.add(currentChannelLabel);
564         // textArea = new ListBox();
565         // import gtk.ScrolledWindow;
566 
567         // ScrolledWindow scrollTextChats = new ScrolledWindow(textArea);
568         // textBox.add(scrollTextChats);
569         // import gtk.TextView;
570         // textBox.add(new TextView());
571         
572 
573         // import gtk.TextView;
574         // TextView f = new TextView();
575         // textBox.add(f);
576         
577         notebookSwitcher = new Notebook();
578         notebookSwitcher.setScrollable(true);
579         //notebookSwitcher.add(newnew Label("test"));
580 
581         box.add(channelBox);
582         box.add(notebookSwitcher);
583         // box.add(textBox);
584         //box.packEnd(notebookSwitcher,0,0,0);
585 
586         // textBox.setChildPacking(scrollTextChats, true, true, 0, GtkPackType.START);
587         box.setChildPacking(notebookSwitcher, true, true, 0, GtkPackType.START);
588         
589         
590 
591         return box;
592     }
593 
594     private int getPageNum()
595     {
596         return gui.notebook.pageNum(box);
597     }
598 
599     public void shutdown()
600     {
601         /* This is called from gui.d */
602         int pageNum = getPageNum();
603 
604         if(pageNum == -1)
605         {
606             /* TODO: Error handling */
607         }
608         else
609         {
610             gui.notebook.removePage(pageNum);
611             gui.notebook.showAll();
612         }
613     }
614 }