1 module nbtd.NBTList;
2 
3 import std.bitmanip;
4 import std.zlib;
5 import std.format;
6 
7 import nbtd;
8 
9 /// A List containing unnamed NBT Items.
10 class NBTList : INBTItem
11 {
12 private:
13 	string _name;
14 	NBTType _elementType;
15 	INBTItem[] _items;
16 public:
17 	this(INBTItem[] items = [], string name = "")
18 	{
19 		_name = name;
20 		if(items.length > 0)
21 		{
22 			_elementType = items[0].type;
23 			value = items;
24 		}
25 	}
26 
27 	@property NBTType type() { return NBTType.List; }
28 	@property int size() { int len = 0; for(int i = 0; i < _items.length; i++) len += _items[i].size; return 5 + len; }
29 
30 	@property string name() { return _name; }
31 	@property void name(string name) { assert(name.length < short.max, "Name is too long! (%s)".format(name.length)); _name = name; }
32 
33 	@property INBTItem[] value() { return _items; }
34 	@property void value(INBTItem[] value)
35 	{
36 		if(_items.length == 0 && value.length > 0)
37 			elementType = value[0].type;
38 		for(int i = 0; i < value.length; i++)
39 			assert(value[i].type == elementType);
40 		_items = value[];
41 	}
42 
43 	/// Gets/Sets the type of the array. Will automatically get overwritten when array length is 0 and new values are assigned.
44 	@property ref elementType() { return _elementType; }
45 
46 	/// Returns: the item at the index `index`
47 	T get(T : INBTItem = INBTItem)(size_t index)
48 	{
49 		return cast(T)_items[index];
50 	}
51 
52 	ubyte[] encode(bool compressed = true, bool hasName = true)
53 	{
54 		ubyte[] data;
55 		if(hasName) data = new ubyte[3 + name.length + size];
56 		else data = new ubyte[size];
57 
58 		int dataIndex = 0;
59 
60 		if(hasName)
61 		{
62 			data[0] = cast(ubyte)type;
63 
64 			data.write!short(cast(short)name.length, 1);
65 			data[3 .. 3 + name.length] = cast(ubyte[])name;
66 
67 			data[3 + name.length] = cast(ubyte)elementType;
68 			data.write!int(cast(int)value.length, 4 + name.length);
69 			dataIndex = 8 + name.length;
70 		}
71 		else
72 		{
73 			data[0] = cast(ubyte)elementType;
74 			data.write!int(cast(int)value.length, 1);
75 			dataIndex = 5;
76 		}
77 
78 		for(int i = 0; i < value.length; i++)
79 		{
80 			auto buffer = value[i].encode(false, false);
81 			data[dataIndex .. dataIndex + buffer.length] = buffer;
82 			dataIndex += buffer.length;
83 		}
84 
85 		if(compressed)
86 		{
87 			return compressGZip(data);
88 		}
89 		return data;
90 	}
91 
92 	void decode(ubyte[] data, bool compressed = true, bool hasName = true)
93 	{
94 		if(compressed)
95 		{
96 			data = uncompressGZip(data);
97 		}
98 		read(data, hasName);
99 	}
100 
101 	void read(ref ubyte[] stream, bool hasName = true)
102 	{
103 		_name = "";
104 		if(hasName)
105 		{
106 			assert(stream.read!ubyte == type);
107 			short nameLength = stream.read!short;
108 			_name = cast(string)stream[0 .. nameLength];
109 			stream = stream[nameLength .. $];
110 		}
111 		elementType = cast(NBTType)stream.read!ubyte;
112 		int arrLength = stream.read!int;
113 		_items.length = 0;
114 		for(int i = 0; i < arrLength; i++)
115 			_items ~= parseElement(elementType, stream, false);
116 	}
117 
118 	@property INBTItem dup()
119 	{
120 		auto copy = new NBTList();
121 		copy.name = name;
122 		copy.value = value;
123 		return copy;
124 	}
125 
126 	/// Returns: the list with at most 100 characters width
127 	override string toString()
128 	{
129 		return toString(100);
130 	}
131 
132 	/// Returns: the list with a specified line width.
133 	/// Returns: `"NBTList('name') = [each element.toString]"`
134 	/// Returns: if longer than `lineLength` will return `"NBTList('name') = [each el..."`
135 	string toString(int lineLength)
136 	{
137 		string items = format("%s", value);
138 		if(items.length > lineLength - 21)
139 			return format("NBTList('%s') = %s...", name, items[0 .. lineLength - 21]);
140 		return format("NBTList('%s') = %s", name, items);
141 	}
142 
143 	/// Duplicates the List and appends an item to it if operator is `~`.
144 	/// Otherwise `static assert(0)`
145 	NBTList opBinary(string op)(INBTItem item)
146 	{
147 		static if(op == "~")
148 		{
149 			NBTList copy = new NBTList();
150 			copy.name = name;
151 			copy.value = value ~ item;
152 			return copy;
153 		}
154 		else static assert(0, "Operator " ~ op ~ " is not implemented!");
155 	}
156 
157 	/// Appends an item to `this` if operator is `~`.
158 	/// Otherwise `static assert(0)`
159 	NBTList opOpAssign(string op)(INBTItem item)
160 	{
161 		static if(op == "~")
162 		{
163 			if(_value.length == 0)
164 				elementType = item.type;
165 			assert(item.type == elementType);
166 			_value ~= item;
167 			return this;
168 		}
169 		else static assert(0, "Operator " ~ op ~ " is not implemented!");
170 	}
171 
172 	/// Appends multiple items to `this` if operator is `~`.
173 	/// Otherwise `static assert(0)`
174 	NBTList opOpAssign(string op)(INBTItem[] items)
175 	{
176 		static if(op == "~")
177 		{
178 			if(items.length == 0)
179 				return this;
180 			if(_value.length == 0)
181 				elementType = items[0].type;
182 			foreach(item; items)
183 				assert(item.type == elementType);
184 			_value ~= items;
185 			return this;
186 		}
187 		else static assert(0, "Operator " ~ op ~ " is not implemented!");
188 	}
189 
190 	/// Returns: the item at index `index`
191 	INBTItem opIndex(size_t index)
192 	{
193 		return _items[index];
194 	}
195 
196 	/// Returns: a duplicate of `this.value`
197 	INBTItem[] opIndex()
198 	{
199 		return _items[];
200 	}
201 
202 	/// Returns: a slice of `this.value`
203 	INBTItem[] opSlice(size_t start, size_t end)
204 	{
205 		return _items[start .. end];
206 	}
207 
208 	/// Returns: the length of `this.value`
209 	/// See_Also: length
210 	size_t opDollar()
211 	{
212 		return _items.length;
213 	}
214 
215 	/// Returns: the length of `this.value`
216 	@property size_t length()
217 	{
218 		return _items.length;
219 	}
220 }