1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Dargon.Data;
using Dargon.RADS;
using ItzWarty;

namespace Dargon.Resources
{
   /// <summary>
   /// Represents a generic resource node - a node in a tree which represents an elements of
   /// the file system (whether it be a directory or a file).
   /// 
   /// This node implementation is optimized in that we expect the following:
   ///   - The resource tree is loaded and then mostly remains unmodified for the remainder of its
   ///     lifetime
   ///   - Adding a resource node can be a slow operation (In our case, O(1)), whereas finding a 
   ///     node by name should be a fast operation (In our case, O(1)).  
   ///   - A node's parent can change from NULL to Value.  However, once it is set to value, its 
   ///     parent will not be mutated.  This allows methods such as GetResourcePath to have cached
   ///     return values.  We assume GetResourcePath will not be invoked until AFTER we have 
   ///     entirely loaded our tree (in actuality, it is valid to call GetResourcePath when our 
   ///     node's parents have all been linked together).  This is another huge optimization, as
   ///     before, we were concatenating N strings without using a string buffer, every time that
   ///     GetResourcePath was invoked.
   /// 
   /// Resource Nodes are Sorted and IDed as required by the Dargon Service Protocol.  
   /// We will expose a method, Sort(), which will sort the resource node.  The Children property
   /// may only be accessed when the internal child list is known to be sorted.  Otherwise, the
   /// UnsortedChildren property must be used.  Additionally, the AddChild and HasChild methods may
   /// be used to mutate our resource node.  The Children property, however, may not be used until
   /// Sort is called after that.
   /// </summary>
   public abstract class ResourceNode
   {
      //-------------------------------------------------------------------------------------------
      // Node Implementation Stuff
      //-------------------------------------------------------------------------------------------
      /// <summary>
      /// Our parent node, which is guaranteed to be a resource node
      /// </summary>
      protected ResourceNode m_parent = null;

      /// <summary>
      /// Our child nodes are stored into a hash map so that we can quickly resolve a resource name
      /// to a resource node (That's O(1)).  Dictionary internally uses GetHashCode.
      /// </summary>
      protected Dictionary<string, ResourceNode> m_childrenMap = new Dictionary<string, ResourceNode>();

      /// <summary>
      /// Our child nodes are also stored into an unordered list, which allows for really fast 
      /// constant-time insertion.  Eventually, this list is sorted when .Sort() is called, and then
      /// nodes are given identities (pre-order style)
      /// </summary>
      protected List<ResourceNode> m_children = new List<ResourceNode>();

      //-------------------------------------------------------------------------------------------
      // Readonly Children List Implementation
      //-------------------------------------------------------------------------------------------
      /// <summary>
      /// Represents the sort state of our node's direct descendent collection.
      /// This sort state does not represent the sort state of any other children.
      /// </summary>
      private NodeSortState m_sortState = NodeSortState.Dirty;

      //-------------------------------------------------------------------------------------------
      // ResourceNode specific stuff (ie: Tag implementation, resourcePath caching)
      //-------------------------------------------------------------------------------------------
      /// <summary>
      /// Gets the name of the given resource node.
      /// Examples: Program Files (x86)
      ///           Annie.dds
      ///           RiotExternalImageResource.swf
      /// 
      /// Dargon Resource Nodes should not be renamed after initialization.
      /// </summary>
      public string ResourceName { get { return m_resourceName; } }
      private readonly string m_resourceName;

      /// <summary>
      /// The hash of our resource name, which can be used for fast lookup.
      /// </summary>
      public int ResourceNameHash { get { return m_resourceNameHash; } }
      private readonly int m_resourceNameHash;

      /// <summary>
      /// The data type of our resource node.
      /// </summary>
      private readonly ResourceNodeType m_resourceNodeType = ResourceNodeType.Undefined;

      /// <summary>
      /// Resource nodes can have tags assigned to them via resourceNode[tagName] = value.  
      /// However, we avoid the overhead of creating a new map for every resource node by
      /// leaving this field uninitialized.  It is instantiated when our indexer is first
      /// called.
      /// </summary>
      private Dictionary<string, object> m_tagDictionary;

      /// <summary>
      /// Our fully qualified resourcePath, including our root node.
      /// This field is calculated once at GetResourcePath's first call, and its result
      /// is cached afterwards.
      /// </summary>
      private string m_resourcePath = null;

      /// <summary>
      /// The hash value of m_resourcePath.
      /// This field is calculated once at GetResourcePath's first call, and its result
      /// is cached afterwards.
      /// </summary>
      private uint m_resourcePathHash = 0;

      /// <summary>
      /// Our resource path without the root node included.
      /// This field is calculated once at GetResourcePathWithoutRoot's first call, and its result
      /// is cached afterwards.
      /// </summary>
      private string m_resourcePathWithoutRoot = null;

      /// <summary>
      /// The hash value of m_resourcePathWithoutRoot.
      /// This field is calculated once at GetResourcePathWithoutRoot's first call, and its result
      /// is cached afterwards.
      /// </summary>
      private uint m_resourcePathWithoutRootHash = 0;

      /// <summary>
      /// The breadcrumbs leading up to and including this node.  This field is set once when
      /// we first call GetBreadCrumbs, and should not be mutated after that.
      /// </summary>
      private ReadOnlyCollection<ResourceNode> m_breadCrumbs;

      /// <summary>
      /// This method is invoked to get the data source of our resource node.  This private field 
      /// is set at the constructor.  This is most equivalent to a member method call in c++; the
      /// parameter of this method is simply the thisPointer to the subclass of ResourceNode's
      /// instance.
      /// </summary>
      private readonly Func<ResourceNode, IDataSource> m_dataSourceGetter;

      //-------------------------------------------------------------------------------------------
      // ResourceNode Properties and Methods for Generic Nodes
      //-------------------------------------------------------------------------------------------
      /// <summary>
      /// Initializes a new instance of the ResourceNode class which initially has no child/parent.
      /// </summary>
      /// <param name="resourceName">
      /// The name of our resource node.  This should not be a fully qualified name.
      /// </param>
      /// <param name="nodeType">
      /// The type of node represented by this resource node.
      /// </param>
      /// <param name="dataSourceGetter">
      /// This parameter should be specified if the resource node's type has the content flag set.
      /// The method's signature is (IDataSource)(ResourceNode this); such a calling pattern is 
      /// necessary as passing in the this pointer to the base class constructor cannot be done.
      /// </param>
      protected ResourceNode(
         string resourceName, 
         ResourceNodeType nodeType,
         Func<ResourceNode, IDataSource> dataSourceGetter = null)
      {
         m_resourceName = resourceName;
         m_resourceNameHash = resourceName.ToUpper().GetHashCode();
         m_resourceNodeType = nodeType;
         m_dataSourceGetter = dataSourceGetter;

         if (m_resourceNodeType.HasFlag(ResourceNodeType.Content))
            if (m_dataSourceGetter == null)
               throw new Exception("Resource Node {0} was marked as Content Node but did not specify data source getter in constructor".F(resourceName));
      }

      /// <summary>
      /// Marks the node and its descendents for sorting.
      /// After this method is called, marked nodes' Children property becomes available.  
      /// When the Children getter of the marked node is called, the node's children will be lazily
      /// sorted.  
      /// </summary>
      public void LazySort()
      {
         m_sortState = NodeSortState.LazySort;
         foreach (var child in m_children)
            child.LazySort();
      }

      /// <summary>
      /// Sorts the given resource node and its contents.
      /// Note: This method will sort ALL the descendents of this node as well.  Use it with
      /// caution.  In general, this method should only be called by plugins/resource node
      /// loaders.
      /// </summary>
      /// <param name="sortDescendents">
      /// Whether or not all our descendents will be sorted.  If this is false, then only our direct
      /// descendents are sorted.
      /// </param>
      public void Sort(bool sortDescendents = false)
      {
         //----------------------------------------------------------------------------------
         // We use ordinal sorting as we're working with ASCII characters for our resource
         // node paths (with the exception of the roots of AIR paths, which CAN have unicode
         // characters.  Ordinal sorting is pretty close to c++'s strcmp, so it's fast.
         //----------------------------------------------------------------------------------
         if (m_sortState != NodeSortState.Sorted)
         {
            m_children.Sort(
               (a, b) => {
                  bool aHasChildren = a.HasChildren;
                  bool bHasChildren = b.HasChildren;
                  if (aHasChildren && !bHasChildren)
                     return 1;
                  if (!aHasChildren && bHasChildren)
                     return -1;
                  else
                     return -String.Compare(a.m_resourceName, b.m_resourceName, StringComparison.OrdinalIgnoreCase);
               }
            );
            m_sortState = NodeSortState.Sorted;
         }

         if (sortDescendents)
         {
            foreach (var child in m_children)
               child.Sort();
         }
      }

      //-------------------------------------------------------------------------------------------
      // Hierarchal relationship management.  Note that children and parents cannot be unassigned
      // in this tree implementation.  The resource node trees which we load are like that, and
      // this special rule permits us to do various large optimizations.
      //-------------------------------------------------------------------------------------------
      /// <summary>
      /// Gets the child nodes of our resource node as a readonly collection.
      /// If the children list is currently unsorted, then this call will throw; instead, you must
      /// use the UnsortedChildren property.
      /// In general, ResourceNode implementations should use the ChildrenUnsorted property, while
      /// other modules should use the Children property.  
      /// There is no performance gain in using the Children property over the ChildrenUnsorted
      /// property.
      /// </summary>
      public ReadOnlyCollection<ResourceNode> Children
      {
         get
         {
            if (m_sortState == NodeSortState.Dirty)
               throw new Exception("Attempted to access Dirty Children Collection of resource node.  Consider calling Sort() or using the UnsortedChildren property");
            else if (m_sortState == NodeSortState.LazySort)
               Sort(false);

            // This doesn't have much overhead - ReadOnlyCollection just wraps around our collection
            return m_children.AsReadOnly();
         }
      }
      /// <summary>
      /// Gets the child nodes of our resource node as a readonly collection with no sorting guarantee.
      /// There is no performance gain in using the Children property over the ChildrenUnsorted
      /// property.
      /// </summary>
      public ReadOnlyCollection<ResourceNode> UnsortedChildren
      {
         get
         {
            // This doesn't have much overhead - ReadOnlyCollection just wraps around our collection
            return m_children.AsReadOnly();
         }
      }

      /// <summary>
      /// Gets whether or not this resource node has children nodes.
      /// This method may be called while the resource node's children list is dirty.
      /// </summary>
      protected bool HasChildren { get { return m_children.Any(); } }

      /// <summary>
      /// Returns whether or not we have the given child node.
      /// This method may be caled while the resource node's children list is dirty.
      /// </summary>
      /// <param name="childName"></param>
      public bool HasChild(string childName)
      {
         return m_childrenMap.ContainsKey(childName);
      }

      /// <summary>
      /// Gets the given child node, or null if the child doesn't exist.
      /// This method is much more efficient than 
      /// </summary>
      /// <param name="name"></param>
      public ResourceNode GetChildOrNull(string name)
      {
         ResourceNode result = null;
         if (m_childrenMap.TryGetValue(name, out result))
            return result;
         else
            return null;
      }

      /// <summary>
      /// Adds a child to our resource node.
      /// This method may be caled while the resource node's children list is dirty.
      /// </summary>
      public void AddChild(ResourceNode newChild)
      {
         //----------------------------------------------------------------------------------------
         // First off, we insert into our m_children unordered list.  Then, we insert into our
         // hashmap.  The list allows for fast insertion, while the hashmap allows for fast name->
         // node resolution.
         //----------------------------------------------------------------------------------------
         m_children.Add(newChild);
         m_childrenMap.Add(newChild.m_resourceName, newChild);
         newChild.Parent = this;

         if (m_sortState == NodeSortState.Sorted)
            m_sortState = NodeSortState.Dirty;
      }

      /// <summary>
      /// Gets the parent node of our resource node.  This property can only be updated once.
      /// The parent node's parent SHALL NOT be mutated after this call, or the m_resourcePath
      /// field will be invalid after this call.
      /// </summary>
      public ResourceNode Parent
      {
         get
         {
            return m_parent;
         }
         set
         {
            if (m_parent == null)
            {
               m_parent = value;
            }
            else
            {
               throw new Exception(
                  "Tried to redefine the parent of {0} from non-null value".F(m_resourceName)
               );
            }
         }
      }

      //-------------------------------------------------------------------------------------------
      // ResourceNode Properties and Methods Specific to ResourceNode (as opposed to generic Nodes)
      // :: Simple Getters
      //-------------------------------------------------------------------------------------------
      /// <summary>
      /// Whether or not this ResourceNode can supply a data source via the DataSource property.
      /// </summary>
      public bool IsContentNode { get { return m_resourceNodeType.HasFlag(ResourceNodeType.Content); } }

      /// <summary>
      /// Whether or not this ResourceNode is in the tree of a RAF Archive's contents
      /// </summary>
      public bool IsRAFNode { get { return m_resourceNodeType.HasFlag(ResourceNodeType.RAF); } }

      /// <summary>
      /// Whether or not this ResourceNode represents the root node of a RAF Archive tree.
      /// </summary>
      public bool IsRAFArchiveNode { get { return m_resourceNodeType.HasFlag(ResourceNodeType.RAFArchive); } }

      /// <summary>
      /// Whether or not IO Operations with this resource node are slow, meaning that they have one
      /// factor which makes their performance low (ie: Disk IO, decompression).
      /// </summary>
      public bool IsSlowOperations { get { return m_resourceNodeType.HasFlag(ResourceNodeType.SlowIO); } }

      /// <summary>
      /// Whether or not IO Operations with this resource node are very slow, meaning that they 
      /// have two or more factor which makes their performance low (ie: Disk IO + decompression).
      /// </summary>
      public bool IsVerySlowOperations { get { return m_resourceNodeType.HasFlag(ResourceNodeType.VerySlowIO); } }

      /// <summary>
      /// The Resource Node Type which can provide useful descriptions of the capabilities of this
      /// resource node.
      /// </summary>
      public ResourceNodeType NodeType { get { return m_resourceNodeType; } }

      //-------------------------------------------------------------------------------------------
      // ResourceNode Properties and Methods Specific to ResourceNode (as opposed to generic Nodes)
      // :: Calculated-then-Cached Values
      //-------------------------------------------------------------------------------------------
      /// <summary>
      /// Gets the path of this resource.
      /// Examples: 0.0.0.25/DATA/Characters/Annie/Annie.dds
      ///           C:/Riot Games/League of Legends/RADS/projects/lol_air_client/releases/0.0.0.134/deploy/assets/swfs/RiotExternalImageResource.swf
      /// </summary>
      public string GetResourcePath()
      {
         string result;
         if (m_resourcePath != null)
         {
            result = m_resourcePath;
         }
         else
         {
            if (m_parent != null)
               result = m_parent.GetResourcePath() + "/" + ResourceName;
            else
               result = ResourceName;

            m_resourcePath = result;
            m_resourcePathHash = RAFHashManager.GetHash(m_resourcePath);
         }
         return result;
      }

      /// <summary>
      /// Gets the hash of our resource path
      /// </summary>
      /// <returns></returns>
      public uint GetResourcePathHash()
      {
         GetResourcePath();
         return m_resourcePathHash;
      }

      /// <summary>
      /// Gets the path of this resource, without the root node
      /// Examples: DATA/Characters/Annie/Annie.dds
      ///           /Riot Games/League of Legends/RADS/projects/lol_air_client/releases/0.0.0.134/deploy/assets/swfs/RiotExternalImageResource.swf
      /// </summary>
      public string GetResourcePathWithoutRoot()
      {
         string result;
         if (m_resourcePathWithoutRoot != null)
            result = m_resourcePathWithoutRoot;
         else
         {
            if (m_parent != null)
            {
               if (m_parent.m_parent != null)
                  result = m_parent.GetResourcePathWithoutRoot() + "/" + ResourceName;
               else
                  result = ResourceName;
            }
            else
            {
               result = ResourceName;
            }
            m_resourcePathWithoutRoot = result;
            m_resourcePathWithoutRootHash = RAFHashManager.GetHash(m_resourcePathWithoutRoot);
         }
         return result;
      }

      /// <summary>
      /// Gets the hash of our resource path
      /// </summary>
      /// <returns></returns>
      public uint GetResourcePathWithoutRootHash()
      {
         GetResourcePathWithoutRoot();
         return m_resourcePathWithoutRootHash;
      }

      /// <summary>
      /// You can index a resource node with a string to store data relevant to a certain extension
      /// The data will not be saved between Dargon Manager sessions.
      /// </summary>
      public object this[string index]
      {
         get
         {
            if (m_tagDictionary == null || !m_tagDictionary.ContainsKey(index))
               return null;
            return m_tagDictionary[index];
         }
         set
         {
            if (m_tagDictionary == null) 
               m_tagDictionary = new Dictionary<string, object>();
            if (m_tagDictionary.ContainsKey(index))
               m_tagDictionary[index] = value;
            else
               m_tagDictionary.Add(index, value);
         }
      }

      /// <summary>
      /// The Content Node datasource provides methods for acquiring the content of our 
      /// resourcenode.  
      /// </summary>
      public IDataSource DataSource
      {
         get
         {
            if (IsContentNode)
            {
               return m_dataSourceGetter(this);
            }
            else
            {
               throw new Exception(
                  "Tried to get the content datasource of non-content node " + m_resourceName);
            }
         }
      }

      /// <summary>
      /// Gets the breadcrumbs leading up to and including this node
      /// </summary>
      public ReadOnlyCollection<ResourceNode> GetBreadCrumbs()
      {
         if (m_breadCrumbs != null)
            return m_breadCrumbs;
         else
         {
            var result = new List<ResourceNode>();
            ResourceNode currentNode = this;
            while (currentNode != null)
            {
               result.Add(currentNode);
               currentNode = currentNode.Parent;
            }
            result.Reverse();
            m_breadCrumbs = result.AsReadOnly();
            return m_breadCrumbs;
         }
      }

      /// <summary>
      /// Gets the highest node in the node's heirarchy.
      /// </summary>
      public ResourceNode GetTopMostNode()
      {
         ResourceNode currentNode = this;
         while (currentNode.m_parent != null)
            currentNode = currentNode.m_parent;
         return currentNode;
      }

      /// <summary>
      /// Gets all the leaves in this node's heirarchy.
      /// This value cannot be cached, as traversal is downwards into the tree; therefore, it 
      /// should be noted that this method should be used sparingly.  
      /// </summary>
      /// <param name="includePluginedNodes">Not Implemented</param>
      /// <param name="excludePluginNodes">Not Implemented</param>
      public List<ResourceNode> GetLeaves(
         bool includePluginedNodes = true, 
         bool excludePluginNodes = true)
      {
         // The to-do list is used to figure out what remaining nodes need to be iterated through
         Stack<ResourceNode> todoList = new Stack<ResourceNode>();
         todoList.Push(this);

         //Arraylist can be inserted into at constant time.
         List<ResourceNode> leaves = new List<ResourceNode>();
         while (todoList.Any())
         {
            var node = todoList.Pop();
            if (!node.Children.Any()) //If the node has no children, it's a leaf
            {
               leaves.Add(node);
            }
            else //Otherwise, its children might be leaves.
            {
               foreach (var child in node.m_children)
                  todoList.Push(child);
            }
         }
         return leaves;
      }

      /// <summary>
      /// Clones this resource node, creating a copy of its values, while not respecting its 
      /// heirarchal relationship with its parent.  
      /// </summary>
      /// <param name="cloneChildren">
      /// Whether or not the node's children should be cloned as well.
      /// </param>
      /// <returns>The cloned resource node</returns>
      public virtual ResourceNode Clone(bool cloneChildren = true)
      {
         throw new Exception("Cloning a resource node of type " + GetType() + " is not supported");
      }
   }
}