context: using System;
  using System.IO;
  using System.Collections.Generic;
  
  namespace Weland {
      interface ISerializableBE {
  	void Load(BinaryReaderBE reader);
  	void Save(BinaryWriterBE writer);
      }
  
      public static class World {
  	public const short One = 1024;
  	public static short FromDouble(double d) {
  	    return (short) Math.Round(d * World.One);
  	}
  
  	public static double ToDouble(short w) {
  	    return ((double) w / World.One);
  	}
  
  	public static double ToDouble(int i) {
  	    return ((double) i / World.One);
  	}
      }
  
      public static class Angle {
  	const short AngularPrecision = 512;
  	public static short FromDouble(double d) {
  	    return (short) Math.Round(d * AngularPrecision / 360);
  	}
  	public static double ToDouble(short a) {
  	    return ((double) a * 360 / AngularPrecision);
  	}
      }
  
      public partial class Level {
  	public List<Point> Endpoints = new List<Point> ();
  	public List<Line> Lines = new List<Line>();
  	public List<Polygon> Polygons = new List<Polygon>();
  	public List<MapObject> Objects = new List<MapObject>();
  	public List<Side> Sides = new List<Side>();
  	public List<Platform> Platforms = new List<Platform>();
  	public List<Light> Lights = new List<Light>();
  	public Dictionary<uint, byte[]> Chunks = new Dictionary<uint, byte[]>();
  	public List<Placement> ItemPlacement = new List<Placement>();
  	public List<Placement> MonsterPlacement = new List<Placement>();
  	public List<Annotation> Annotations = new List<Annotation>();
  	public List<Media> Medias = new List<Media>();
  	public List<AmbientSound> AmbientSounds = new List<AmbientSound>();
  	public List<RandomSound> RandomSounds = new List<RandomSound>();
  
  	MapInfo mapInfo = new MapInfo();
  
  	// stuff for the editor, not saved to file
  	public short TemporaryLineStartIndex = -1;
  	public Point TemporaryLineEnd;
  
          public short VisualModePolygonIndex = -1;
          public Point VisualModePoint;
  
  	// for hiding points
  	public List<HashSet<Polygon>> EndpointPolygons = new List<HashSet<Polygon>>();
  	public List<HashSet<Line>> EndpointLines = new List<HashSet<Line>>();
  
  	List<uint> ChunkFilter = new List<uint> {
  	    // saved game / optimized map tags
  	    Endpoint.Tag,
  	    Wadfile.Chunk("plyr"),
  	    Wadfile.Chunk("dwol"),
  	    Wadfile.Chunk("mobj"),
  	    Wadfile.Chunk("door"),
  	    Wadfile.Chunk("iidx"),
  	    Wadfile.Chunk("alin"),
  	    Wadfile.Chunk("apol"),
  	    Wadfile.Chunk("mOns"),
  	    Wadfile.Chunk("fx  "),
  	    Wadfile.Chunk("bang"),
  	    Platform.DynamicTag,
  	    Wadfile.Chunk("weap"),
  	    Wadfile.Chunk("cint"),
  	    Wadfile.Chunk("slua"),
  
  	    // embedded physics
  	    Wadfile.Chunk("MNpx"),
  	    Wadfile.Chunk("FXpx"),
  	    Wadfile.Chunk("PRpx"),
  	    Wadfile.Chunk("RXpx"),
  	    Wadfile.Chunk("WPpx"),
  	};
  
  	void LoadChunk(ISerializableBE chunk, byte[] data) {
  	    chunk.Load(new BinaryReaderBE(new MemoryStream(data)));
  	}
  	
  	byte[] SaveChunk(ISerializableBE chunk) {
  	    MemoryStream stream = new MemoryStream();
  	    BinaryWriterBE writer = new BinaryWriterBE(stream);
  	    chunk.Save(writer);
  	    return stream.ToArray();
  	}
  
  	public Level() {
  	    // build a Forge-style light list
  	    for (int i = 0; i <= 20; ++i) {
  		Light light = new Light((double) (20 - i) / 20);
  		Lights.Add(light);
  	    }
  
  	    for (int i = 0; i < Placement.Count; ++i) {
  		ItemPlacement.Add(new Placement());
  		MonsterPlacement.Add(new Placement());
  	    }
  	}
  
  	void LoadChunkList<T>(List<T> list, byte[] data) where T : ISerializableBE, new() {
  	    BinaryReaderBE reader = new BinaryReaderBE(new MemoryStream(data));
  	    list.Clear();
  	    while (reader.BaseStream.Position < reader.BaseStream.Length) {
  		T t = new T();
  		t.Load(reader);
  		list.Add(t);
  	    }
  	}
  
  	byte[] SaveChunk<T>(List<T> list) where T : ISerializableBE, new() {
  		MemoryStream stream = new MemoryStream();
  		BinaryWriterBE writer = new BinaryWriterBE(stream);
  		foreach (T t in list) {
  		    t.Save(writer);
  		}
  		
  		return stream.ToArray();
  	    }
  	
  	public void Load(Wadfile.DirectoryEntry wad) {
  	    Chunks = wad.Chunks;
  	    
  	    if (wad.Chunks.ContainsKey(MapInfo.Tag)) {
  		LoadChunk(mapInfo, wad.Chunks[MapInfo.Tag]);
  	    } else {
  		throw new Wadfile.BadMapException("Incomplete level: missing map info chunk");
  	    }
  
  	    if (wad.Chunks.ContainsKey(Point.Tag)) {
  		LoadChunkList<Point>(Endpoints, wad.Chunks[Point.Tag]);
  	    } else if (wad.Chunks.ContainsKey(Endpoint.Tag)) {
  		Endpoints.Clear();
  		List<Endpoint> endpointList = new List<Endpoint>();
  		LoadChunkList<Endpoint>(endpointList, wad.Chunks[Endpoint.Tag]);
  		foreach (Endpoint e in endpointList) {
  		    Endpoints.Add(e.Vertex);
  		}
  	    } else {
  		throw new Wadfile.BadMapException("Incomplete level: missing points chunk");
  	    }
  
  	    if (wad.Chunks.ContainsKey(Line.Tag)) {
  		LoadChunkList<Line>(Lines, wad.Chunks[Line.Tag]);
  	    } else {
  		throw new Wadfile.BadMapException("Incomplete level: missing lines chunk");
  	    }
  
  	    if (wad.Chunks.ContainsKey(Polygon.Tag)) {
  		LoadChunkList<Polygon>(Polygons, wad.Chunks[Polygon.Tag]);
  	    } else {
  		throw new Wadfile.BadMapException("Incomplete level: missing polygons chunk");
  	    }
  
  	    if (wad.Chunks.ContainsKey(Side.Tag)) {
  		LoadChunkList<Side>(Sides, wad.Chunks[Side.Tag]);
  	    }
  
  	    if (wad.Chunks.ContainsKey(Platform.StaticTag)) {
  		LoadChunkList<Platform>(Platforms, wad.Chunks[Platform.StaticTag]);
  	    } else if (wad.Chunks.ContainsKey(Platform.DynamicTag)) {
  		BinaryReaderBE reader = new BinaryReaderBE(new MemoryStream(wad.Chunks[Platform.DynamicTag]));
  		Platforms.Clear();
  		while (reader.BaseStream.Position < reader.BaseStream.Length) {
  		    Platform platform = new Platform();
  		    platform.LoadDynamic(reader);
  		    Platforms.Add(platform);
  
  		    // open up the polygon
  		    if (platform.PolygonIndex >= 0 && platform.PolygonIndex < Polygons.Count) {
  			Polygon polygon = Polygons[platform.PolygonIndex];
  			if (platform.ComesFromFloor) {
  			    polygon.FloorHeight = platform.MinimumHeight;
  			}
  			if (platform.ComesFromCeiling) {
  			    polygon.CeilingHeight = platform.MaximumHeight;
  			}
  		    }
  		}
  	    }
  	    
  	    if (wad.Chunks.ContainsKey(Light.Tag)) {
  		LoadChunkList<Light>(Lights, wad.Chunks[Light.Tag]);
  	    }
  
  	    if (wad.Chunks.ContainsKey(Placement.Tag)) {
  		BinaryReaderBE reader = new BinaryReaderBE(new MemoryStream(wad.Chunks[Placement.Tag]));
  		ItemPlacement.Clear();
  		for (int i = 0; i < Placement.Count; ++i) {
  		    Placement placement = new Placement();
  		    placement.Load(reader);
  		    ItemPlacement.Add(placement);
  		}
  
  		MonsterPlacement.Clear();
  		for (int i = 0; i < Placement.Count; ++i) {
  
gt: 		    Placement placement = new Placement();