1 module nbtd.NBTCompound;
2 
3 import nbtd;
4 
5 import std.zlib;
6 import std.bitmanip;
7 import std.format;
8 
9 /// NBT Compound containing a array of named items.
10 class NBTCompound : INBTItem
11 {
12 private:
13 	string _name;
14 	INBTItem[] _value;
15 public:
16 	this(INBTItem[] items = [], string name = "")
17 	{
18 		_name = name;
19 		_value = items;
20 	}
21 
22 	@property NBTType type() { return NBTType.Compound; }
23 	@property int size()
24 	{
25 		int size = 1; // incl. EndTag
26 		foreach(INBTItem item; _value)
27 			size += item.size + 3 + item.name.length;
28 		return size;
29 	}
30 
31 	@property string name() { return _name; }
32 	@property void name(string name) { assert(name.length < short.max, "Name is too long! (%s)".format(name.length)); _name = name; }
33 
34 	@property INBTItem[] value() { return _value; }
35 	@property void value(INBTItem[] value) { _value = value; }
36 
37 	/// Returns: the item with the name `name`.
38 	/// Params:
39 	/// 	name =			Name to search for.
40 	/// 	throwOnError =	When false, returns null instead of throwing an exception.
41 	/// Throws: Exception when unable to cast to the target type.
42 	/// Throws: Exception when unable to find `name` in the array.
43 	T get(T : INBTItem = INBTItem)(string name, bool throwOnError = true)
44 	{
45 		foreach(INBTItem item; _value)
46 			if(item.name == name)
47 			{
48 				if(cast(T)item)
49 					return cast(T)item;
50 				if(throwOnError)
51 					throw new Exception("Can't cast item to " ~ T.stringof);
52 				return null;
53 			}
54 		if(throwOnError)
55 			throw new Exception("Item " ~ name ~ " not found in Compound!");
56 		return null;
57 	}
58 
59 	ubyte[] encode(bool compressed = true, bool hasName = true)
60 	{
61 		ubyte[] data;
62 		if(hasName)
63 			data = new ubyte[size + 3 + _name.length];
64 		else
65 			data = new ubyte[size];
66 
67 		if(hasName)
68 		{
69 			data[0] = cast(ubyte)type;
70 			data.write(cast(short)_name.length, 1);
71 			data[3 .. 3 + _name.length] = cast(ubyte[])_name;
72 			int index = 3 + name.length;
73 			for(int i = 0; i < _value.length; i++)
74 			{
75 				ubyte[] buffer = _value[i].encode(false);
76 				data[index .. index + buffer.length] = buffer;
77 				index += buffer.length;
78 			}
79 			data[index] = cast(ubyte)0;
80 			assert(index == data.length - 1);
81 		}
82 		else
83 		{
84 			int index = 0;
85 			for(int i = 0; i < _value.length; i++)
86 			{
87 				ubyte[] buffer = _value[i].encode(false);
88 				data[index .. index + buffer.length] = buffer;
89 				index += buffer.length;
90 			}
91 			data[index] = cast(ubyte)0;
92 			assert(index == data.length - 1);
93 		}
94 
95 		if(compressed)
96 		{
97 			return compressGZip(data);
98 		}
99 		return data;
100 	}
101 
102 	void decode(ubyte[] data, bool compressed = true, bool hasName = true)
103 	{
104 		if(compressed)
105 		{
106 			data = uncompressGZip(data);
107 		}
108 
109 		read(data, hasName);
110 	}
111 
112 	void read(ref ubyte[] stream, bool hasName = true)
113 	{
114 		_name = "";
115 		if(hasName)
116 		{
117 			assert(stream.read!ubyte == cast(ubyte)type);
118 			short nameLength = stream.read!short;
119 			_name = cast(string)stream[0 .. nameLength];
120 			stream = stream[nameLength .. $];
121 		}
122 		INBTItem item;
123 		_value.length = 0;
124 		while((item = parseElement(stream)).type != NBTType.End)
125 			_value ~= item;
126 	}
127 
128 	@property INBTItem dup()
129 	{
130 		auto copy = new NBTCompound();
131 		copy.name = name;
132 		copy.value = value;
133 		return copy;
134 	}
135 
136 	/// Returns: `"NBTCompound('name') = {[each ChildElement.toString()]}"`
137 	override string toString()
138 	{
139 		return format("NBTCompound('%s') = {%s}", name, value);
140 	}
141 
142 	/// See_Also: get
143 	INBTItem opIndex(string index)
144 	{
145 		return get(index);
146 	}
147 
148 	/// Sets the value at `index` to a duplicate of item and will rename the item name to `index`.
149 	INBTItem opIndexAssign(INBTItem item, string index)
150 	{
151 		int found = -1;
152 		item = item.dup;
153 		item.name = index;
154 		foreach(int i, INBTItem val; _value)
155 			if(val.name == index)
156 				found = i;
157 		if(found == -1)
158 		{
159 			_value ~= item;
160 		}
161 		else
162 		{
163 			_value[found] = item;
164 		}
165 		return item;
166 	}
167 }