StarterPack.cs
namespace PixelPusher;

public static class StarterPack
{
	public static PixelDoc BlankSprite()
	{
		return new PixelDoc( 16, 16, Palettes.Default )
		{
			Name = "coin"
		};
	}

	public static PixelDoc Coin()
	{
		var doc = new PixelDoc( 16, 16, Palettes.Default ) { Name = "coin" };
		FillCircle( doc, 8, 8, 6, 7 );
		FillCircle( doc, 8, 8, 5, 14 );
		FillCircle( doc, 8, 8, 3, 7 );
		Ring( doc, 8, 8, 6, 11 );
		return doc;
	}

	public static TilesetDoc CornerStore()
	{
		var sheet = new PixelDoc( 128, 64, Palettes.Default ) { Name = "corner_store" };
		var set = new TilesetDoc( sheet, 16 )
		{
			Name = "Corner Store"
		};

		DrawTile( sheet, 1, Sidewalk );
		DrawTile( sheet, 2, Brick );
		DrawTile( sheet, 3, Crate );
		DrawTile( sheet, 4, Glass );
		DrawTile( sheet, 5, ShelfTile );
		DrawTile( sheet, 6, Neon );
		DrawTile( sheet, 7, Cooler );
		DrawTile( sheet, 8, OneWay );
		DrawTile( sheet, 9, Spring );
		DrawTile( sheet, 10, Hazard );
		DrawTile( sheet, 11, BrickDark );
		DrawTile( sheet, 12, FloorTan );
		DrawTile( sheet, 13, Awning );
		DrawTile( sheet, 14, Dumpster );
		DrawTile( sheet, 15, Stripe );
		DrawTile( sheet, 16, Lamp );

		set.Flags[1] = TileFlag.Solid;
		set.Flags[2] = TileFlag.Solid;
		set.Flags[3] = TileFlag.Solid;
		set.Flags[4] = TileFlag.Solid;
		set.Flags[5] = TileFlag.Solid;
		set.Flags[6] = TileFlag.Solid;
		set.Flags[7] = TileFlag.Solid;
		set.Flags[8] = TileFlag.OneWay;
		set.Flags[9] = TileFlag.Spring;
		set.Flags[10] = TileFlag.Hazard;
		set.Flags[11] = TileFlag.Solid;
		set.Flags[12] = TileFlag.None;
		set.Flags[13] = TileFlag.None;
		set.Flags[14] = TileFlag.Solid;
		set.Flags[15] = TileFlag.None;
		set.Flags[16] = TileFlag.None;
		return set;
	}

	public static StageDoc TutorialStage()
	{
		var stage = new StageDoc( 48, 18, 16 ) { Name = "Stock the Cooler" };
		for ( var x = 0; x < stage.Width; x++ )
		{
			stage.SetTile( x, 16, 1 );
			stage.SetTile( x, 17, 2 );
		}

		for ( var x = 6; x <= 10; x++ )
			stage.SetTile( x, 13, 8 );
		for ( var x = 14; x <= 18; x++ )
			stage.SetTile( x, 11, 5 );
		for ( var x = 22; x <= 26; x++ )
			stage.SetTile( x, 12, 3 );
		for ( var x = 28; x <= 31; x++ )
			stage.SetTile( x, 9, 8 );

		stage.SetTile( 20, 15, 9 );
		stage.SetTile( 33, 15, 10 );
		stage.SetTile( 34, 15, 10 );

		for ( var y = 12; y <= 15; y++ )
			stage.SetTile( 40, y, 7 );
		stage.SetTile( 41, 15, 6 );
		stage.SetTile( 42, 15, 4 );
		stage.SetTile( 43, 15, 4 );

		stage.Goal = "grab";
		stage.PlaceEntity( "start", 2, 15 );
		stage.PlaceEntity( "sign", 4, 15, "STOCK THE COOLER" );
		stage.PlaceEntity( "coin", 8, 12 );
		stage.PlaceEntity( "coin", 16, 10 );
		stage.PlaceEntity( "coin", 29, 8 );
		stage.PlaceEntity( "spring", 20, 15 );
		stage.PlaceEntity( "check", 21, 15 );
		stage.PlaceEntity( "exit", 42, 14 );
		return stage;
	}

	public static StageDoc NightShift()
	{
		var stage = new StageDoc( 52, 18, 16 ) { Name = "Night Shift", Goal = "exit" };
		Ground( stage );
		Row( stage, 8, 12, 13, 8 );
		Row( stage, 16, 19, 16, 0 );
		Row( stage, 16, 19, 17, 0 );
		stage.SetTile( 15, 15, 9 );
		Row( stage, 21, 25, 14, 3 );
		Row( stage, 28, 31, 15, 10 );
		Row( stage, 34, 39, 12, 8 );
		Row( stage, 41, 44, 10, 5 );
		stage.SetTile( 46, 15, 14 );
		stage.SetTile( 47, 15, 16 );
		Row( stage, 48, 50, 15, 7 );
		stage.PlaceEntity( "start", 2, 15 );
		stage.PlaceEntity( "sign", 4, 15, "NIGHT SHIFT" );
		stage.PlaceEntity( "coin", 10, 12 );
		stage.PlaceEntity( "spring", 15, 15 );
		stage.PlaceEntity( "check", 22, 13 );
		stage.PlaceEntity( "coin", 36, 11 );
		stage.PlaceEntity( "coin", 42, 9 );
		stage.PlaceEntity( "exit", 49, 14 );
		return stage;
	}

	public static StageDoc BackLot()
	{
		var stage = new StageDoc( 44, 16, 16 ) { Name = "Back Lot", Goal = "grab" };
		Ground( stage, 14, 15 );
		Row( stage, 10, 13, 14, 0 );
		Row( stage, 10, 13, 15, 0 );
		Row( stage, 20, 23, 14, 0 );
		Row( stage, 20, 23, 15, 0 );
		Row( stage, 30, 33, 14, 0 );
		Row( stage, 30, 33, 15, 0 );
		stage.SetTile( 9, 13, 9 );
		stage.SetTile( 19, 13, 9 );
		stage.SetTile( 29, 13, 9 );
		Row( stage, 14, 17, 11, 8 );
		stage.SetTile( 36, 13, 14 );
		stage.SetTile( 37, 13, 13 );
		stage.PlaceEntity( "start", 2, 13 );
		stage.PlaceEntity( "sign", 4, 13, "BACK LOT" );
		stage.PlaceEntity( "coin", 11, 10 );
		stage.PlaceEntity( "coin", 21, 9 );
		stage.PlaceEntity( "coin", 31, 9 );
		stage.PlaceEntity( "spring", 9, 13 );
		stage.PlaceEntity( "spring", 19, 13 );
		stage.PlaceEntity( "check", 24, 13 );
		stage.PlaceEntity( "exit", 40, 12 );
		return stage;
	}

	public static StageDoc ClosedSundays()
	{
		var stage = new StageDoc( 36, 22, 16 ) { Name = "Closed Sundays", Goal = "exit" };
		Ground( stage, 20, 21 );
		Row( stage, 6, 9, 17, 3 );
		Row( stage, 12, 16, 15, 8 );
		Row( stage, 4, 7, 13, 5 );
		Row( stage, 18, 22, 12, 3 );
		Row( stage, 24, 28, 10, 8 );
		Row( stage, 14, 17, 8, 5 );
		Row( stage, 29, 33, 7, 7 );
		stage.SetTile( 10, 19, 9 );
		stage.SetTile( 23, 19, 10 );
		stage.SetTile( 24, 19, 10 );
		stage.SetTile( 8, 16, 16 );
		stage.SetTile( 20, 11, 13 );
		stage.PlaceEntity( "start", 2, 19 );
		stage.PlaceEntity( "sign", 4, 19, "CLOSED SUNDAYS" );
		stage.PlaceEntity( "coin", 7, 16 );
		stage.PlaceEntity( "coin", 14, 14 );
		stage.PlaceEntity( "coin", 20, 11 );
		stage.PlaceEntity( "coin", 26, 9 );
		stage.PlaceEntity( "spring", 10, 19 );
		stage.PlaceEntity( "spring", 27, 9 );
		stage.PlaceEntity( "check", 19, 11 );
		stage.PlaceEntity( "exit", 31, 6 );
		return stage;
	}

	public static readonly (string Id, string Title, string Kind, string When)[] Shelf =
	{
		("starter", "Stock the Cooler", "pixel.project", "tour"),
		("night-shift", "Night Shift", "pixel.stage", "sample"),
		("back-lot", "Back Lot", "pixel.stage", "sample"),
		("closed-sundays", "Closed Sundays", "pixel.stage", "sample"),
		("corner-store", "Corner Store", "pixel.tileset", "tiles"),
		("coin", "Coin", "pixel.sprite", "sprite")
	};

	public static string NextStageId( string name )
	{
		var order = new[] { "Stock the Cooler", "Night Shift", "Back Lot", "Closed Sundays" };
		var i = Array.IndexOf( order, name );
		if ( i < 0 || i >= order.Length - 1 )
			return null;
		return i switch
		{
			0 => "night-shift",
			1 => "back-lot",
			2 => "closed-sundays",
			_ => null
		};
	}

	public static bool LoadShelf( string id )
	{
		switch ( id )
		{
			case "starter":
				Desk.Sprite = Coin();
				Desk.Tileset = CornerStore();
				Desk.Stage = TutorialStage();
				Desk.Chip = new ChipDoc();
				Desk.Name = "Stock the Cooler";
				break;
			case "night-shift":
				EnsureKit();
				Desk.Stage = NightShift();
				Desk.Name = Desk.Stage.Name;
				break;
			case "back-lot":
				EnsureKit();
				Desk.Stage = BackLot();
				Desk.Name = Desk.Stage.Name;
				break;
			case "closed-sundays":
				EnsureKit();
				Desk.Stage = ClosedSundays();
				Desk.Name = Desk.Stage.Name;
				break;
			case "corner-store":
				Desk.Tileset = CornerStore();
				Desk.Name = "Corner Store";
				break;
			case "coin":
				Desk.Sprite = Coin();
				Desk.Name = "Coin";
				break;
			default:
				return false;
		}

		Desk.Touch();
		return true;
	}

	static void EnsureKit()
	{
		Desk.Tileset ??= CornerStore();
		Desk.Sprite ??= Coin();
		Desk.Chip ??= new ChipDoc();
	}

	static void Ground( StageDoc stage, int walk = 16, int fill = 17 )
	{
		for ( var x = 0; x < stage.Width; x++ )
		{
			stage.SetTile( x, walk, 1 );
			if ( fill < stage.Height )
				stage.SetTile( x, fill, 2 );
		}
	}

	static void Row( StageDoc stage, int x0, int x1, int y, int id )
	{
		for ( var x = x0; x <= x1; x++ )
			stage.SetTile( x, y, id );
	}

	public static void BlitTerry( Color32[] dst, int dw, int dh, int dx, int dy, bool faceLeft, int frame )
	{
		var pal = Palettes.Default.Colors;
		var src = frame == 1 ? TerryWalk : TerryIdle;
		for ( var y = 0; y < 16; y++ )
		for ( var x = 0; x < 16; x++ )
		{
			var sx = faceLeft ? 15 - x : x;
			var i = src[y * 16 + sx];
			if ( i == 0 )
				continue;
			var px = dx + x;
			var py = dy + y;
			if ( (uint)px >= (uint)dw || (uint)py >= (uint)dh )
				continue;
			dst[py * dw + px] = pal[i];
		}
	}

	public static void BlitCoin( Color32[] dst, int dw, int dh, int dx, int dy, float t )
	{
		var pal = Palettes.Default.Colors;
		var bob = (int)MathF.Round( MathF.Sin( t * 6f ) );
		for ( var y = 0; y < 16; y++ )
		for ( var x = 0; x < 16; x++ )
		{
			var i = CoinPx[y * 16 + x];
			if ( i == 0 )
				continue;
			var px = dx + x;
			var py = dy + y + bob;
			if ( (uint)px >= (uint)dw || (uint)py >= (uint)dh )
				continue;
			dst[py * dw + px] = pal[i];
		}
	}

	static readonly byte[] TerryIdle = BuildTerry( false );
	static readonly byte[] TerryWalk = BuildTerry( true );
	static readonly byte[] CoinPx = BuildCoin();

	static byte[] BuildTerry( bool walk )
	{
		var p = new byte[16 * 16];
		void D( int x, int y, byte c )
		{
			if ( (uint)x < 16 && (uint)y < 16 )
				p[y * 16 + x] = c;
		}

		for ( var y = 1; y <= 5; y++ )
		for ( var x = 5; x <= 10; x++ )
			D( x, y, 7 );
		D( 6, 3, 1 );
		D( 9, 3, 1 );
		D( 7, 4, 11 );
		for ( var y = 6; y <= 10; y++ )
		for ( var x = 5; x <= 10; x++ )
			D( x, y, 12 );
		D( 4, 7, 6 );
		D( 11, 7, 6 );
		var ly = walk ? 12 : 11;
		var ry = walk ? 11 : 12;
		D( 6, ly, 3 );
		D( 6, ly + 1, 3 );
		D( 6, ly + 2, 9 );
		D( 9, ry, 3 );
		D( 9, ry + 1, 3 );
		D( 9, ry + 2, 9 );
		return p;
	}

	static byte[] BuildCoin()
	{
		var doc = Coin();
		return PixelDoc.CopyBytes( doc.Indices );
	}

	delegate void TilePainter( PixelDoc doc, int ox, int oy );

	static void DrawTile( PixelDoc sheet, int id, TilePainter paint )
	{
		var col = 8;
		var ox = (id % col) * 16;
		var oy = (id / col) * 16;
		paint( sheet, ox, oy );
	}

	static void Sidewalk( PixelDoc d, int ox, int oy )
	{
		Fill( d, ox, oy, 16, 16, 15 );
		HLine( d, ox, oy, 16, 5 );
		HLine( d, ox, oy + 8, 16, 4 );
		VLine( d, ox + 8, oy, 16, 4 );
	}

	static void Brick( PixelDoc d, int ox, int oy )
	{
		Fill( d, ox, oy, 16, 16, 11 );
		HLine( d, ox, oy + 4, 16, 10 );
		HLine( d, ox, oy + 9, 16, 10 );
		HLine( d, ox, oy + 14, 16, 10 );
		VLine( d, ox + 7, oy, 5, 10 );
		VLine( d, ox + 3, oy + 5, 5, 10 );
		VLine( d, ox + 12, oy + 10, 5, 10 );
	}

	static void BrickDark( PixelDoc d, int ox, int oy )
	{
		Fill( d, ox, oy, 16, 16, 10 );
		HLine( d, ox, oy + 5, 16, 1 );
		HLine( d, ox, oy + 11, 16, 1 );
	}

	static void Crate( PixelDoc d, int ox, int oy )
	{
		Fill( d, ox, oy, 16, 16, 11 );
		Rect( d, ox, oy, 15, 15, 7 );
		d.SetIndex( ox + 8, oy + 8, 14 );
	}

	static void Glass( PixelDoc d, int ox, int oy )
	{
		Fill( d, ox, oy, 16, 16, 12 );
		for ( var i = 2; i < 14; i++ )
			d.SetIndex( ox + i, oy + i / 2, 5 );
		Rect( d, ox, oy, 15, 15, 4 );
	}

	static void ShelfTile( PixelDoc d, int ox, int oy )
	{
		Fill( d, ox, oy, 16, 16, 10 );
		HLine( d, ox, oy + 3, 16, 11 );
		HLine( d, ox, oy + 8, 16, 11 );
		HLine( d, ox, oy + 13, 16, 11 );
		d.SetIndex( ox + 4, oy + 6, 6 );
		d.SetIndex( ox + 11, oy + 11, 8 );
	}

	static void Neon( PixelDoc d, int ox, int oy )
	{
		Fill( d, ox, oy, 16, 16, 1 );
		Rect( d, ox + 1, oy + 1, 13, 13, 6 );
		Fill( d, ox + 3, oy + 3, 10, 10, 6 );
		HLine( d, ox + 2, oy + 8, 12, 9 );
	}

	static void Cooler( PixelDoc d, int ox, int oy )
	{
		Fill( d, ox, oy, 16, 16, 3 );
		Rect( d, ox, oy, 15, 15, 12 );
		Fill( d, ox + 3, oy + 3, 10, 8, 12 );
		HLine( d, ox + 3, oy + 12, 10, 8 );
	}

	static void OneWay( PixelDoc d, int ox, int oy )
	{
		HLine( d, ox, oy + 4, 16, 8 );
		HLine( d, ox, oy + 5, 16, 13 );
		for ( var x = 0; x < 16; x += 4 )
			d.SetIndex( ox + x, oy + 3, 8 );
	}

	static void Spring( PixelDoc d, int ox, int oy )
	{
		Fill( d, ox + 3, oy + 10, 10, 6, 6 );
		HLine( d, ox + 2, oy + 9, 12, 9 );
		HLine( d, ox + 4, oy + 6, 8, 6 );
		HLine( d, ox + 5, oy + 3, 6, 9 );
	}

	static void Hazard( PixelDoc d, int ox, int oy )
	{
		Fill( d, ox, oy + 8, 16, 8, 11 );
		for ( var x = 0; x < 16; x += 2 )
			d.SetIndex( ox + x, oy + 7 - (x % 4), 6 );
	}

	static void FloorTan( PixelDoc d, int ox, int oy )
	{
		Fill( d, ox, oy, 16, 16, 11 );
		for ( var y = 0; y < 16; y += 4 )
		for ( var x = (y / 4) % 2; x < 16; x += 2 )
			d.SetIndex( ox + x, oy + y, 7 );
	}

	static void Awning( PixelDoc d, int ox, int oy )
	{
		Fill( d, ox, oy + 4, 16, 6, 6 );
		HLine( d, ox, oy + 3, 16, 9 );
		HLine( d, ox, oy + 10, 16, 10 );
		for ( var x = 0; x < 16; x += 4 )
			VLine( d, ox + x, oy + 4, 6, 8 );
	}

	static void Dumpster( PixelDoc d, int ox, int oy )
	{
		Fill( d, ox, oy + 4, 16, 12, 3 );
		Rect( d, ox, oy + 4, 15, 11, 12 );
		HLine( d, ox + 2, oy + 3, 12, 5 );
		d.SetIndex( ox + 3, oy + 8, 8 );
		d.SetIndex( ox + 12, oy + 8, 8 );
	}

	static void Stripe( PixelDoc d, int ox, int oy )
	{
		Fill( d, ox, oy, 16, 16, 2 );
		HLine( d, ox, oy + 7, 16, 7 );
		HLine( d, ox, oy + 8, 16, 7 );
	}

	static void Lamp( PixelDoc d, int ox, int oy )
	{
		VLine( d, ox + 8, oy + 6, 10, 4 );
		Fill( d, ox + 5, oy + 1, 6, 5, 7 );
		d.SetIndex( ox + 8, oy, 14 );
	}

	static void Fill( PixelDoc d, int x, int y, int w, int h, byte c )
	{
		for ( var yy = 0; yy < h; yy++ )
		for ( var xx = 0; xx < w; xx++ )
			d.SetIndex( x + xx, y + yy, c );
	}

	static void HLine( PixelDoc d, int x, int y, int w, byte c )
	{
		for ( var i = 0; i < w; i++ )
			d.SetIndex( x + i, y, c );
	}

	static void VLine( PixelDoc d, int x, int y, int h, byte c )
	{
		for ( var i = 0; i < h; i++ )
			d.SetIndex( x, y + i, c );
	}

	static void Rect( PixelDoc d, int x, int y, int w, int h, byte c )
	{
		HLine( d, x, y, w, c );
		HLine( d, x, y + h, w, c );
		VLine( d, x, y, h, c );
		VLine( d, x + w, y, h, c );
	}

	static void FillCircle( PixelDoc d, int cx, int cy, int r, byte c )
	{
		var r2 = r * r;
		for ( var y = -r; y <= r; y++ )
		for ( var x = -r; x <= r; x++ )
		{
			if ( x * x + y * y <= r2 )
				d.SetIndex( cx + x, cy + y, c );
		}
	}

	static void Ring( PixelDoc d, int cx, int cy, int r, byte c )
	{
		for ( var a = 0; a < 360; a += 6 )
		{
			var rad = a * MathF.PI / 180f;
			var x = cx + (int)MathF.Round( MathF.Cos( rad ) * r );
			var y = cy + (int)MathF.Round( MathF.Sin( rad ) * r );
			d.SetIndex( x, y, c );
		}
	}
}