Files @ r28375:2cad5fe66365
Branch filter:

Location: cpp/openttd-patchpack/source/src/train_gui.cpp - annotation

Jonathan G Rennison
Fix #11644: Off by one error/buffer over-read in StrMakeValid (#11645)

* Fix #11644: Off by one error in StrMakeValid UTF-8 decode overrun detection

* Fix #11644: Off by one error in StrMakeValid buffer last character

* Fix: Unnecessary string duplication at StrMakeValid call sites
  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
r12768:980ae0491352
r12768:980ae0491352
r12768:980ae0491352
r12768:980ae0491352
r12768:980ae0491352
r12768:980ae0491352
r12768:980ae0491352
r9111:983de9c5a848
r6422:5983361e241a
r5584:545d748cc681
r8107:82461791b7a2
r8116:df67d3c5e4fd
r5584:545d748cc681
r8114:866ed489ed98
r8144:1432edd15267
r21806:6d9f5fa48e29
r26103:316b73a1be08
r5584:545d748cc681
r8264:d493cb51fe8a
r8264:d493cb51fe8a
r21383:942c32fb8b0e
r21383:942c32fb8b0e
r17624:f2c5f47dceaa
r17624:f2c5f47dceaa
r17624:f2c5f47dceaa
r26125:9a63e6c142ba
r17624:f2c5f47dceaa
r17624:f2c5f47dceaa
r27942:f7389062d120
r5584:545d748cc681
r14220:2b66d13ca715
r5584:545d748cc681
r6422:5983361e241a
r23607:36c15679007d
r23966:9de0a0960701
r20030:d2dff80f1917
r23607:36c15679007d
r12030:bf346482c342
r5584:545d748cc681
r5584:545d748cc681
r5584:545d748cc681
r6422:5983361e241a
r23607:36c15679007d
r12450:fcc3ae606967
r6422:5983361e241a
r26907:b8856ea20ba4
r9297:6cd3e74fc38e
r5584:545d748cc681
r5584:545d748cc681
r5584:545d748cc681
r5584:545d748cc681
r15243:5330fd7c5e76
r15243:5330fd7c5e76
r15243:5330fd7c5e76
r26625:305bf9c06c64
r15243:5330fd7c5e76
r22422:11fa7af547be
r15243:5330fd7c5e76
r15243:5330fd7c5e76
r26625:305bf9c06c64
r15243:5330fd7c5e76
r16431:ec558deca9d7
r15243:5330fd7c5e76
r15243:5330fd7c5e76
r22715:e4c3ea780fe1
r23607:36c15679007d
r23607:36c15679007d
r19435:df583417ceb5
r15243:5330fd7c5e76
r24597:afde5721a3b6
r24597:afde5721a3b6
r24597:afde5721a3b6
r15243:5330fd7c5e76
r15243:5330fd7c5e76
r26625:305bf9c06c64
r26625:305bf9c06c64
r26625:305bf9c06c64
r26625:305bf9c06c64
r26625:305bf9c06c64
r15243:5330fd7c5e76
r15243:5330fd7c5e76
r15243:5330fd7c5e76
r15243:5330fd7c5e76
r15243:5330fd7c5e76
r15243:5330fd7c5e76
r12364:cbc66d4e5c75
r13599:f6ef46132fe6
r26606:9fda8d0f0d5a
r13060:e57594b0ca84
r13599:f6ef46132fe6
r15243:5330fd7c5e76
r12364:cbc66d4e5c75
r26606:9fda8d0f0d5a
r5584:545d748cc681
r16431:ec558deca9d7
r13602:30f9b380fb13
r13602:30f9b380fb13
r26823:cb65f12c003b
r5584:545d748cc681
r5584:545d748cc681
r5584:545d748cc681
r26606:9fda8d0f0d5a
r5584:545d748cc681
r28310:7fedf4bea966
r5584:545d748cc681
r26823:cb65f12c003b
r26823:cb65f12c003b
r26823:cb65f12c003b
r26823:cb65f12c003b
r26823:cb65f12c003b
r26823:cb65f12c003b
r26823:cb65f12c003b
r26823:cb65f12c003b
r26823:cb65f12c003b
r26823:cb65f12c003b
r26823:cb65f12c003b
r26823:cb65f12c003b
r26823:cb65f12c003b
r26823:cb65f12c003b
r26823:cb65f12c003b
r26823:cb65f12c003b
r26823:cb65f12c003b
r5584:545d748cc681
r26823:cb65f12c003b
r26823:cb65f12c003b
r26823:cb65f12c003b
r26823:cb65f12c003b
r26823:cb65f12c003b
r26823:cb65f12c003b
r26823:cb65f12c003b
r26823:cb65f12c003b
r15243:5330fd7c5e76
r26823:cb65f12c003b
r26823:cb65f12c003b
r26823:cb65f12c003b
r26823:cb65f12c003b
r26823:cb65f12c003b
r26823:cb65f12c003b
r26823:cb65f12c003b
r26823:cb65f12c003b
r26823:cb65f12c003b
r26823:cb65f12c003b
r26823:cb65f12c003b
r26823:cb65f12c003b
r12377:643da76b9d87
r26823:cb65f12c003b
r12377:643da76b9d87
r5584:545d748cc681
r26823:cb65f12c003b
r26823:cb65f12c003b
r26823:cb65f12c003b
r5584:545d748cc681
r12377:643da76b9d87
r5584:545d748cc681
r5584:545d748cc681
r5584:545d748cc681
r5584:545d748cc681
r26606:9fda8d0f0d5a
r26606:9fda8d0f0d5a
r26606:9fda8d0f0d5a
r5584:545d748cc681
r5584:545d748cc681
r5584:545d748cc681
r13808:32b982f51e30
r13808:32b982f51e30
r13808:32b982f51e30
r13808:32b982f51e30
r13808:32b982f51e30
r13808:32b982f51e30
r13808:32b982f51e30
r13808:32b982f51e30
r19944:25a78576fb5e
r18782:6453522c2154
r13808:32b982f51e30
r13808:32b982f51e30
r13808:32b982f51e30
r23523:6c7a73ae36c5
r23523:6c7a73ae36c5
r23523:6c7a73ae36c5
r23523:6c7a73ae36c5
r23523:6c7a73ae36c5
r23523:6c7a73ae36c5
r13808:32b982f51e30
r13808:32b982f51e30
r26261:1ed997ddf7e6
r26261:1ed997ddf7e6
r13808:32b982f51e30
r13808:32b982f51e30
r23538:8df50944b27a
r13808:32b982f51e30
r13808:32b982f51e30
r13808:32b982f51e30
r11443:81f75d8ddd0c
r11443:81f75d8ddd0c
r11443:81f75d8ddd0c
r13808:32b982f51e30
r11443:81f75d8ddd0c
r11443:81f75d8ddd0c
r11443:81f75d8ddd0c
r11443:81f75d8ddd0c
r13808:32b982f51e30
r5584:545d748cc681
r19120:272cdd873f1d
r13808:32b982f51e30
r13808:32b982f51e30
r13808:32b982f51e30
r13808:32b982f51e30
r13808:32b982f51e30
r13808:32b982f51e30
r19120:272cdd873f1d
r27421:e8c2cdc1e8e6
r5584:545d748cc681
r13808:32b982f51e30
r27245:43b55d4c0b1f
r5584:545d748cc681
r5584:545d748cc681
r11443:81f75d8ddd0c
r11443:81f75d8ddd0c
r11443:81f75d8ddd0c
r11443:81f75d8ddd0c
r11443:81f75d8ddd0c
r11443:81f75d8ddd0c
r11443:81f75d8ddd0c
r11443:81f75d8ddd0c
r11443:81f75d8ddd0c
r5584:545d748cc681
r5868:bc3e720ec5f7
r26839:2bed12525c08
r7002:201ff6832d3a
r20439:d34496be8ad3
r5584:545d748cc681
r26839:2bed12525c08
r5584:545d748cc681
r7002:201ff6832d3a
r20439:d34496be8ad3
r5584:545d748cc681
r5584:545d748cc681
r5584:545d748cc681
r11443:81f75d8ddd0c
r11443:81f75d8ddd0c
r11443:81f75d8ddd0c
r13808:32b982f51e30
r11443:81f75d8ddd0c
r11443:81f75d8ddd0c
r11443:81f75d8ddd0c
r11443:81f75d8ddd0c
r13808:32b982f51e30
r13808:32b982f51e30
r19120:272cdd873f1d
r27421:e8c2cdc1e8e6
r19120:272cdd873f1d
r19120:272cdd873f1d
r19120:272cdd873f1d
r19120:272cdd873f1d
r19120:272cdd873f1d
r19120:272cdd873f1d
r19120:272cdd873f1d
r19120:272cdd873f1d
r19120:272cdd873f1d
r19120:272cdd873f1d
r19120:272cdd873f1d
r13808:32b982f51e30
r13808:32b982f51e30
r13808:32b982f51e30
r19944:25a78576fb5e
r13808:32b982f51e30
r13808:32b982f51e30
r13808:32b982f51e30
r27911:e2da1b3040ed
r5584:545d748cc681
r27911:e2da1b3040ed
r13808:32b982f51e30
r19120:272cdd873f1d
r13808:32b982f51e30
r13808:32b982f51e30
r27926:4743d7f21c0e
r13808:32b982f51e30
r27421:e8c2cdc1e8e6
r13808:32b982f51e30
r27911:e2da1b3040ed
r27911:e2da1b3040ed
r27911:e2da1b3040ed
r13808:32b982f51e30
r13808:32b982f51e30
r13808:32b982f51e30
r13808:32b982f51e30
r13808:32b982f51e30
r13808:32b982f51e30
r13808:32b982f51e30
r13808:32b982f51e30
r20188:c7660e0c9b5b
r27882:e0b47acdb939
r23607:36c15679007d
r13808:32b982f51e30
r13808:32b982f51e30
r13808:32b982f51e30
r13808:32b982f51e30
r13808:32b982f51e30
r13808:32b982f51e30
r13808:32b982f51e30
r13808:32b982f51e30
r13808:32b982f51e30
r13808:32b982f51e30
r13808:32b982f51e30
r13808:32b982f51e30
r13808:32b982f51e30
r23607:36c15679007d
r13808:32b982f51e30
r13808:32b982f51e30
r5584:545d748cc681
r5584:545d748cc681
r12069:84bc50fb033a
r12069:84bc50fb033a
r12069:84bc50fb033a
r12069:84bc50fb033a
r12069:84bc50fb033a
r12069:84bc50fb033a
r12069:84bc50fb033a
r5584:545d748cc681
r7530:8eaeedf8b40c
r5584:545d748cc681
r12069:84bc50fb033a
r27424:6a028979ce01
r23607:36c15679007d
r7530:8eaeedf8b40c
r7530:8eaeedf8b40c
r5584:545d748cc681
r27423:4d40205ba623
r5584:545d748cc681
r5584:545d748cc681
r23607:36c15679007d
r27911:e2da1b3040ed
r24597:afde5721a3b6
r13808:32b982f51e30
r13808:32b982f51e30
r26579:321856031c47
r7530:8eaeedf8b40c
r5584:545d748cc681
r5584:545d748cc681
r7530:8eaeedf8b40c
r7530:8eaeedf8b40c
r5584:545d748cc681
r11443:81f75d8ddd0c
r11443:81f75d8ddd0c
r11443:81f75d8ddd0c
r11443:81f75d8ddd0c
r26554:8202c0b49c1d
r12069:84bc50fb033a
r12069:84bc50fb033a
r12069:84bc50fb033a
r11443:81f75d8ddd0c
r27737:728d55b97775
r7530:8eaeedf8b40c
r26554:8202c0b49c1d
r26554:8202c0b49c1d
r21806:6d9f5fa48e29
r28167:b14ea36d49e5
r21806:6d9f5fa48e29
r6422:5983361e241a
r12069:84bc50fb033a
r13608:06bb7056281f
r26554:8202c0b49c1d
r23607:36c15679007d
r27911:e2da1b3040ed
r5584:545d748cc681
r13808:32b982f51e30
r15175:66e0817dc450
r13808:32b982f51e30
r13808:32b982f51e30
r13808:32b982f51e30
r13808:32b982f51e30
r13808:32b982f51e30
r13808:32b982f51e30
r21806:6d9f5fa48e29
r21806:6d9f5fa48e29
r23607:36c15679007d
r26579:321856031c47
r21806:6d9f5fa48e29
r27763:dde0c3a495b8
r22459:c1921e75ce89
r22459:c1921e75ce89
r26554:8202c0b49c1d
r13808:32b982f51e30
r13808:32b982f51e30
r13808:32b982f51e30
r13808:32b982f51e30
r23607:36c15679007d
r5584:545d748cc681
r26579:321856031c47
r13808:32b982f51e30
r13808:32b982f51e30
r13808:32b982f51e30
r13808:32b982f51e30
r12069:84bc50fb033a
r26579:321856031c47
r26554:8202c0b49c1d
r24597:afde5721a3b6
r13808:32b982f51e30
r13808:32b982f51e30
r26554:8202c0b49c1d
r13808:32b982f51e30
r26575:d5725bf82346
r13808:32b982f51e30
r13808:32b982f51e30
r13808:32b982f51e30
r23520:20bbc807b0eb
r26554:8202c0b49c1d
r13808:32b982f51e30
r26554:8202c0b49c1d
r13808:32b982f51e30
r13808:32b982f51e30
r12069:84bc50fb033a
r13808:32b982f51e30
r26554:8202c0b49c1d
r13808:32b982f51e30
r5584:545d748cc681
r13808:32b982f51e30
r23520:20bbc807b0eb
r26554:8202c0b49c1d
r13808:32b982f51e30
r19120:272cdd873f1d
r26554:8202c0b49c1d
r13808:32b982f51e30
r13808:32b982f51e30
r13808:32b982f51e30
r13808:32b982f51e30
r13808:32b982f51e30
r13808:32b982f51e30
r13808:32b982f51e30
r5584:545d748cc681
r5584:545d748cc681
r5584:545d748cc681
r26554:8202c0b49c1d
r27424:6a028979ce01
r27424:6a028979ce01
r12253:9cc92893e2d0
r7530:8eaeedf8b40c
r23607:36c15679007d
r20188:c7660e0c9b5b
r7530:8eaeedf8b40c
r27882:e0b47acdb939
r7530:8eaeedf8b40c
r7530:8eaeedf8b40c
r6422:5983361e241a
r26554:8202c0b49c1d
r21806:6d9f5fa48e29
r13595:3017638cdea0
r26554:8202c0b49c1d
r26582:f8bc4c09d3ec
r28005:30d4958e88dc
r28005:30d4958e88dc
r28005:30d4958e88dc
r28005:30d4958e88dc
r28005:30d4958e88dc
r28005:30d4958e88dc
r28005:30d4958e88dc
r9413:fcf267325763
r28005:30d4958e88dc
r21806:6d9f5fa48e29
r5584:545d748cc681
r5584:545d748cc681
r9943:38700f276db2
r26554:8202c0b49c1d
r5584:545d748cc681
r5584:545d748cc681
/*
 * This file is part of OpenTTD.
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 */

/** @file train_gui.cpp GUI for trains. */

#include "stdafx.h"
#include "window_gui.h"
#include "command_func.h"
#include "train.h"
#include "strings_func.h"
#include "vehicle_func.h"
#include "zoom_func.h"
#include "train_cmd.h"

#include "table/strings.h"

#include "safeguards.h"

/**
 * Callback for building wagons.
 * @param result The result of the command.
 * @param new_veh_id ID of the ne vehicle.
 * @param tile   The tile the command was executed on.
 */
void CcBuildWagon(Commands, const CommandCost &result, VehicleID new_veh_id, uint, uint16_t, CargoArray, TileIndex tile, EngineID, bool, CargoID, ClientID)
{
	if (result.Failed()) return;

	/* find a locomotive in the depot. */
	const Vehicle *found = nullptr;
	for (const Train *t : Train::Iterate()) {
		if (t->IsFrontEngine() && t->tile == tile && t->IsStoppedInDepot()) {
			if (found != nullptr) return; // must be exactly one.
			found = t;
		}
	}

	/* if we found a loco, */
	if (found != nullptr) {
		found = found->Last();
		/* put the new wagon at the end of the loco. */
		Command<CMD_MOVE_RAIL_VEHICLE>::Post(found->tile, new_veh_id, found->index, false);
		InvalidateWindowClassesData(WC_TRAINS_LIST, 0);
	}
}

/**
 * Highlight the position where a rail vehicle is dragged over by drawing a light gray background.
 * @param px        The current x position to draw from.
 * @param max_width The maximum space available to draw.
 * @param y         The vertical centre position to draw from.
 * @param selection Selected vehicle that is dragged.
 * @param chain     Whether a whole chain is dragged.
 * @return The width of the highlight mark.
 */
static int HighlightDragPosition(int px, int max_width, int y, VehicleID selection, bool chain)
{
	bool rtl = _current_text_dir == TD_RTL;

	assert(selection != INVALID_VEHICLE);
	int dragged_width = 0;
	for (Train *t = Train::Get(selection); t != nullptr; t = chain ? t->Next() : (t->HasArticulatedPart() ? t->GetNextArticulatedPart() : nullptr)) {
		dragged_width += t->GetDisplayImageWidth(nullptr);
	}

	int drag_hlight_left = rtl ? std::max(px - dragged_width + 1, 0) : px;
	int drag_hlight_right = rtl ? px : std::min(px + dragged_width, max_width) - 1;
	int drag_hlight_width = std::max(drag_hlight_right - drag_hlight_left + 1, 0);

	if (drag_hlight_width > 0) {
		int height = ScaleSpriteTrad(12);
		int top = y - height / 2;
		Rect r = {drag_hlight_left, top, drag_hlight_right, top + height - 1};
		/* Sprite-scaling is used here as the area is from sprite size */
		GfxFillRect(r.Shrink(ScaleSpriteTrad(1)), _colour_gradient[COLOUR_GREY][7]);
	}

	return drag_hlight_width;
}

/**
 * Draws an image of a whole train
 * @param v         Front vehicle
 * @param r         Rect to draw at
 * @param selection Selected vehicle to draw a frame around
 * @param skip      Number of pixels to skip at the front (for scrolling)
 * @param drag_dest The vehicle another one is dragged over, \c INVALID_VEHICLE if none.
 */
void DrawTrainImage(const Train *v, const Rect &r, VehicleID selection, EngineImageType image_type, int skip, VehicleID drag_dest)
{
	bool rtl = _current_text_dir == TD_RTL;
	Direction dir = rtl ? DIR_E : DIR_W;

	DrawPixelInfo tmp_dpi;
	/* Position of highlight box */
	int highlight_l = 0;
	int highlight_r = 0;
	int max_width = r.Width();

	if (!FillDrawPixelInfo(&tmp_dpi, r)) return;

	{
		AutoRestoreBackup dpi_backup(_cur_dpi, &tmp_dpi);

		int px = rtl ? max_width + skip : -skip;
		int y = r.Height() / 2;
		bool sel_articulated = false;
		bool dragging = (drag_dest != INVALID_VEHICLE);
		bool drag_at_end_of_train = (drag_dest == v->index); // Head index is used to mark dragging at end of train.
		for (; v != nullptr && (rtl ? px > 0 : px < max_width); v = v->Next()) {
			if (dragging && !drag_at_end_of_train && drag_dest == v->index) {
				/* Highlight the drag-and-drop destination inside the train. */
				int drag_hlight_width = HighlightDragPosition(px, max_width, y, selection, _cursor.vehchain);
				px += rtl ? -drag_hlight_width : drag_hlight_width;
			}

			Point offset;
			int width = Train::From(v)->GetDisplayImageWidth(&offset);

			if (rtl ? px + width > 0 : px - width < max_width) {
				PaletteID pal = (v->vehstatus & VS_CRASHED) ? PALETTE_CRASH : GetVehiclePalette(v);
				VehicleSpriteSeq seq;
				v->GetImage(dir, image_type, &seq);
				seq.Draw(px + (rtl ? -offset.x : offset.x), y + offset.y, pal, (v->vehstatus & VS_CRASHED) != 0);
			}

			if (!v->IsArticulatedPart()) sel_articulated = false;

			if (v->index == selection) {
				/* Set the highlight position */
				highlight_l = rtl ? px - width : px;
				highlight_r = rtl ? px - 1 : px + width - 1;
				sel_articulated = true;
			} else if ((_cursor.vehchain && highlight_r != 0) || sel_articulated) {
				if (rtl) {
					highlight_l -= width;
				} else {
					highlight_r += width;
				}
			}

			px += rtl ? -width : width;
		}

		if (dragging && drag_at_end_of_train) {
			/* Highlight the drag-and-drop destination at the end of the train. */
			HighlightDragPosition(px, max_width, y, selection, _cursor.vehchain);
		}
	}

	if (highlight_l != highlight_r) {
		/* Draw the highlight. Now done after drawing all the engines, as
		 * the next engine after the highlight could overlap it. */
		int height = ScaleSpriteTrad(12);
		Rect hr = {highlight_l, 0, highlight_r, height - 1};
		DrawFrameRect(hr.Translate(r.left, CenterBounds(r.top, r.bottom, height)).Expand(WidgetDimensions::scaled.bevel), COLOUR_WHITE, FR_BORDERONLY);
	}
}

/** Helper struct for the cargo details information */
struct CargoSummaryItem {
	CargoID cargo;    ///< The cargo that is carried
	StringID subtype; ///< STR_EMPTY if none
	uint capacity;    ///< Amount that can be carried
	uint amount;      ///< Amount that is carried
	StationID source; ///< One of the source stations

	/** Used by CargoSummary::Find() and similar functions */
	inline bool operator != (const CargoSummaryItem &other) const
	{
		return this->cargo != other.cargo || this->subtype != other.subtype;
	}

	/** Used by std::find() and similar functions */
	inline bool operator == (const CargoSummaryItem &other) const
	{
		return !(this->cargo != other.cargo);
	}
};

static const uint TRAIN_DETAILS_MIN_INDENT  = 32; ///< Minimum indent level in the train details window
static const uint TRAIN_DETAILS_MAX_INDENT  = 72; ///< Maximum indent level in the train details window; wider than this and we start on a new line

/** Container for the cargo summary information. */
typedef std::vector<CargoSummaryItem> CargoSummary;
/** Reused container of cargo details */
static CargoSummary _cargo_summary;

/**
 * Draw the details cargo tab for the given vehicle at the given position
 *
 * @param item  Data to draw
 * @param left  The left most coordinate to draw
 * @param right The right most coordinate to draw
 * @param y     The y coordinate
 */
static void TrainDetailsCargoTab(const CargoSummaryItem *item, int left, int right, int y)
{
	StringID str;
	if (item->amount > 0) {
		SetDParam(0, item->cargo);
		SetDParam(1, item->amount);
		SetDParam(2, item->source);
		SetDParam(3, _settings_game.vehicle.freight_trains);
		str = FreightWagonMult(item->cargo) > 1 ? STR_VEHICLE_DETAILS_CARGO_FROM_MULT : STR_VEHICLE_DETAILS_CARGO_FROM;
	} else {
		str = !IsValidCargoID(item->cargo) ? STR_QUANTITY_N_A : STR_VEHICLE_DETAILS_CARGO_EMPTY;
	}

	DrawString(left, right, y, str, TC_LIGHT_BLUE);
}

/**
 * Draw the details info tab for the given vehicle at the given position
 *
 * @param v     current vehicle
 * @param left  The left most coordinate to draw
 * @param right The right most coordinate to draw
 * @param y     The y coordinate
 */
static void TrainDetailsInfoTab(const Vehicle *v, int left, int right, int y)
{
	if (RailVehInfo(v->engine_type)->railveh_type == RAILVEH_WAGON) {
		SetDParam(0, PackEngineNameDParam(v->engine_type, EngineNameContext::VehicleDetails));
		SetDParam(1, v->value);
		DrawString(left, right, y, STR_VEHICLE_DETAILS_TRAIN_WAGON_VALUE);
	} else {
		SetDParam(0, PackEngineNameDParam(v->engine_type, EngineNameContext::VehicleDetails));
		SetDParam(1, v->build_year);
		SetDParam(2, v->value);
		DrawString(left, right, y, STR_VEHICLE_DETAILS_TRAIN_ENGINE_BUILT_AND_VALUE);
	}
}

/**
 * Draw the details capacity tab for the given vehicle at the given position
 *
 * @param item  Data to draw
 * @param left  The left most coordinate to draw
 * @param right The right most coordinate to draw
 * @param y     The y coordinate
 */
static void TrainDetailsCapacityTab(const CargoSummaryItem *item, int left, int right, int y)
{
	StringID str;
	if (IsValidCargoID(item->cargo)) {
		SetDParam(0, item->cargo);
		SetDParam(1, item->capacity);
		SetDParam(4, item->subtype);
		SetDParam(5, _settings_game.vehicle.freight_trains);
		str = FreightWagonMult(item->cargo) > 1 ? STR_VEHICLE_INFO_CAPACITY_MULT : STR_VEHICLE_INFO_CAPACITY;
	} else {
		/* Draw subtype only */
		SetDParam(0, item->subtype);
		str = STR_VEHICLE_INFO_NO_CAPACITY;
	}
	DrawString(left, right, y, str);
}

/**
 * Collects the cargo transported
 * @param v Vehicle to process
 * @param summary Space for the result
 */
static void GetCargoSummaryOfArticulatedVehicle(const Train *v, CargoSummary &summary)
{
	summary.clear();
	do {
		if (!v->GetEngine()->CanCarryCargo()) continue;

		CargoSummaryItem new_item;
		new_item.cargo = v->cargo_cap > 0 ? v->cargo_type : (CargoID)CT_INVALID;
		new_item.subtype = GetCargoSubtypeText(v);
		if (!IsValidCargoID(new_item.cargo) && new_item.subtype == STR_EMPTY) continue;

		auto item = std::find(std::begin(summary), std::end(summary), new_item);
		if (item == std::end(summary)) {
			item = summary.emplace(std::end(summary));
			item->cargo = new_item.cargo;
			item->subtype = new_item.subtype;
			item->capacity = 0;
			item->amount = 0;
			item->source = INVALID_STATION;
		}

		item->capacity += v->cargo_cap;
		item->amount += v->cargo.StoredCount();
		if (item->source == INVALID_STATION) item->source = v->cargo.GetFirstStation();
	} while ((v = v->Next()) != nullptr && v->IsArticulatedPart());
}

/**
 * Get the length of an articulated vehicle.
 * @param v the vehicle to get the length of.
 * @return the length in pixels.
 */
static uint GetLengthOfArticulatedVehicle(const Train *v)
{
	uint length = 0;

	do {
		length += v->GetDisplayImageWidth();
	} while ((v = v->Next()) != nullptr && v->IsArticulatedPart());

	return length;
}

/**
 * Determines the number of lines in the train details window
 * @param veh_id Train
 * @param det_tab Selected details tab
 * @return Number of line
 */
int GetTrainDetailsWndVScroll(VehicleID veh_id, TrainDetailsWindowTabs det_tab)
{
	int num = 0;

	if (det_tab == TDW_TAB_TOTALS) { // Total cargo tab
		CargoArray max_cargo{};
		for (const Vehicle *v = Vehicle::Get(veh_id); v != nullptr; v = v->Next()) {
			max_cargo[v->cargo_type] += v->cargo_cap;
		}

		num = max_cargo.GetCount();
		num++; // needs one more because first line is description string
	} else {
		for (const Train *v = Train::Get(veh_id); v != nullptr; v = v->GetNextVehicle()) {
			GetCargoSummaryOfArticulatedVehicle(v, _cargo_summary);
			num += std::max(1u, (unsigned)_cargo_summary.size());

			uint length = GetLengthOfArticulatedVehicle(v);
			if (length > (uint)ScaleSpriteTrad(TRAIN_DETAILS_MAX_INDENT)) num++;
		}
	}

	return num;
}

/**
 * Draw the details for the given vehicle at the given position
 *
 * @param v     current vehicle
 * @param r     the Rect to draw within
 * @param vscroll_pos Position of scrollbar
 * @param vscroll_cap Number of lines currently displayed
 * @param det_tab Selected details tab
 */
void DrawTrainDetails(const Train *v, const Rect &r, int vscroll_pos, uint16_t vscroll_cap, TrainDetailsWindowTabs det_tab)
{
	bool rtl = _current_text_dir == TD_RTL;
	int line_height = r.Height();
	int sprite_y_offset = line_height / 2;
	int text_y_offset = (line_height - GetCharacterHeight(FS_NORMAL)) / 2;

	/* draw the first 3 details tabs */
	if (det_tab != TDW_TAB_TOTALS) {
		Direction dir = rtl ? DIR_E : DIR_W;
		int x = rtl ? r.right : r.left;
		for (; v != nullptr && vscroll_pos > -vscroll_cap; v = v->GetNextVehicle()) {
			GetCargoSummaryOfArticulatedVehicle(v, _cargo_summary);

			/* Draw sprites */
			uint dx = 0;
			int px = x;
			const Train *u = v;
			do {
				Point offset;
				int width = u->GetDisplayImageWidth(&offset);
				if (vscroll_pos <= 0 && vscroll_pos > -vscroll_cap) {
					int pitch = 0;
					const Engine *e = Engine::Get(v->engine_type);
					if (e->GetGRF() != nullptr) {
						pitch = ScaleSpriteTrad(e->GetGRF()->traininfo_vehicle_pitch);
					}
					PaletteID pal = (v->vehstatus & VS_CRASHED) ? PALETTE_CRASH : GetVehiclePalette(u);
					VehicleSpriteSeq seq;
					u->GetImage(dir, EIT_IN_DETAILS, &seq);
					seq.Draw(px + (rtl ? -offset.x : offset.x), r.top - line_height * vscroll_pos + sprite_y_offset + pitch, pal, (v->vehstatus & VS_CRASHED) != 0);
				}
				px += rtl ? -width : width;
				dx += width;
				u = u->Next();
			} while (u != nullptr && u->IsArticulatedPart());

			bool separate_sprite_row = (dx > (uint)ScaleSpriteTrad(TRAIN_DETAILS_MAX_INDENT));
			if (separate_sprite_row) {
				vscroll_pos--;
				dx = 0;
			}

			int sprite_width = std::max<int>(dx, ScaleSpriteTrad(TRAIN_DETAILS_MIN_INDENT)) + WidgetDimensions::scaled.hsep_normal;
			Rect dr = r.Indent(sprite_width, rtl);
			uint num_lines = std::max(1u, (unsigned)_cargo_summary.size());
			for (uint i = 0; i < num_lines; i++) {
				if (vscroll_pos <= 0 && vscroll_pos > -vscroll_cap) {
					int py = r.top - line_height * vscroll_pos + text_y_offset;
					if (i > 0 || separate_sprite_row) {
						if (vscroll_pos != 0) GfxFillRect(r.left, py - WidgetDimensions::scaled.matrix.top - 1, r.right, py - WidgetDimensions::scaled.matrix.top, _colour_gradient[COLOUR_GREY][5]);
					}
					switch (det_tab) {
						case TDW_TAB_CARGO:
							if (i < _cargo_summary.size()) {
								TrainDetailsCargoTab(&_cargo_summary[i], dr.left, dr.right, py);
							} else {
								DrawString(dr.left, dr.right, py, STR_QUANTITY_N_A, TC_LIGHT_BLUE);
							}
							break;

						case TDW_TAB_INFO:
							if (i == 0) TrainDetailsInfoTab(v, dr.left, dr.right, py);
							break;

						case TDW_TAB_CAPACITY:
							if (i < _cargo_summary.size()) {
								TrainDetailsCapacityTab(&_cargo_summary[i], dr.left, dr.right, py);
							} else {
								SetDParam(0, STR_EMPTY);
								DrawString(dr.left, dr.right, py, STR_VEHICLE_INFO_NO_CAPACITY);
							}
							break;

						default: NOT_REACHED();
					}
				}
				vscroll_pos--;
			}
		}
	} else {
		int y = r.top;
		CargoArray act_cargo{};
		CargoArray max_cargo{};
		Money feeder_share = 0;

		for (const Vehicle *u = v; u != nullptr; u = u->Next()) {
			act_cargo[u->cargo_type] += u->cargo.StoredCount();
			max_cargo[u->cargo_type] += u->cargo_cap;
			feeder_share             += u->cargo.GetFeederShare();
		}

		/* draw total cargo tab */
		DrawString(r.left, r.right, y + text_y_offset, STR_VEHICLE_DETAILS_TRAIN_TOTAL_CAPACITY_TEXT);
		y += line_height;

		/* Indent the total cargo capacity details */
		Rect ir = r.Indent(WidgetDimensions::scaled.hsep_indent, rtl);
		for (const CargoSpec *cs : _sorted_cargo_specs) {
			CargoID cid = cs->Index();
			if (max_cargo[cid] > 0 && --vscroll_pos < 0 && vscroll_pos > -vscroll_cap) {
				SetDParam(0, cid);            // {CARGO} #1
				SetDParam(1, act_cargo[cid]); // {CARGO} #2
				SetDParam(2, cid);            // {SHORTCARGO} #1
				SetDParam(3, max_cargo[cid]); // {SHORTCARGO} #2
				SetDParam(4, _settings_game.vehicle.freight_trains);
				DrawString(ir.left, ir.right, y + text_y_offset, FreightWagonMult(cid) > 1 ? STR_VEHICLE_DETAILS_TRAIN_TOTAL_CAPACITY_MULT : STR_VEHICLE_DETAILS_TRAIN_TOTAL_CAPACITY);
				y += line_height;
			}
		}
		SetDParam(0, feeder_share);
		DrawString(r.left, r.right, y + text_y_offset, STR_VEHICLE_INFO_FEEDER_CARGO_VALUE);
	}
}