//Część III
//Wzorce strukturalne
  
//Rozdział 7.
//Adapter

public class Point
{
    public int X, Y;
    //inne składowe pominięto
}

public class Line
{
    public Point Start, End;
    //inne składowe pominięto
}

public abstract class VectorObject : Collection<Line> {}

public class VectorRectangle : VectorObject
{
    public VectorRectangle(int x, int y, int width, int height)
    {
        Add(new Line(new Point(x,y), new Point(x+width, y) ));
        Add(new Line(new Point(x+width,y), new Point(x+width, y+height) ));
        Add(new Line(new Point(x,y), new Point(x, y+height) ));
        Add(new Line(new Point(x,y+height), new Point(x+width, y+height) ));
    }
}

//interfejs, który mamy
public static void DrawPoint(Point p)
{
    bitmap.SetPixel(p.X, p.Y, Color.Black);
}

//Adapter

private static readonly List<VectorObject> vectorObjects
    = new List<VectorObject>
{
    new VectorRectangle(1, 1, 10, 10),
    new VectorRectangle(3, 3, 6, 6)
};

public class LineToPointAdapter : Collection<Point>
{
    private static int count = 0;
    public LineToPointAdapter(Line line)
    {
        WriteLine($"{++count}: Generowanie punktów dla linii"
            + $" [{line.Start.X},{line.Start.Y}]-"
            + $"[{line.End.X},{line.End.Y}] (bez buforowania)");
        int left = Math.Min(line.Start.X, line.End.X);
        int right = Math.Max(line.Start.X, line.End.X);
        int top = Math.Min(line.Start.Y, line.End.Y);
        int bottom = Math.Max(line.Start.Y, line.End.Y);
        if (right - left == 0)
        {
            for (int y = top; y <= bottom; ++y)
            {
                Add(new Point(left, y));
            }
        } else if (line.End.Y - line.Start.Y == 0)
        {
            for (int x = left; x <= right; ++x)
            {
                Add(new Point(x, top));
            }
        }
    }
}

private static void DrawPoints()
{
    foreach (var vo in vectorObjects)
    {
        foreach (var line in vo)
        {
            var adapter = new LineToPointAdapter(line);
            adapter.ForEach(DrawPoint);
        }
    }
}

//Tymczasowe stany Adaptera
private static List<Point> points = new List<Point>();
private static bool prepared = false;
private static void Prepare()
{
    if (prepared) return;
    foreach (var vo in vectorObjects)
    {
        foreach (var line in vo)
        {
            var adapter = new LineToPointAdapter(line);
            adapter.ForEach(p => points.Add(p));
        }
    }
    prepared = true;
}

private static void DrawPointsLazy()
{
    Prepare();
    points.ForEach(DrawPoint);
}

public class Point
{
    // tutaj inne składowe
    protected bool Equals(Point other) { ... }
    public override bool Equals(object obj) { ... }
    public override int GetHashCode()
    {
        unchecked { return (X * 397) ^ Y; }
    }
}

public class Line
{
    // tutaj inne składowe
    protected bool Equals(Line other) { ... }
    public override bool Equals(object obj) { ... }
    public override int GetHashCode()
    {
        unchecked
        {
            return ((Start != null ? Start.GetHashCode() : 0) * 397)^ (End != null ? End.GetHashCode() : 0);
        }
    }
}

static Dictionary<int, List<Point>> cache
    = new Dictionary<int, List<Point>>();
 hash = line.GetHashCode();
if (cache.ContainsKey(hash)) return; // linia już jest w buforze

public LineToPointAdapter(Line line)
{
    hash = line.GetHashCode();
    if (cache.ContainsKey(hash)) return; // we already have it
    List<Point> points = new List<Point>();
    //punkty są dodawane do składowej 'points' tak, jak poprzednio, a następnie…

    cache.Add(hash, points);
}

public IEnumerator<Point> GetEnumerator()
{
    return cache[hash].GetEnumerator();
}

//Problem z generowaniem skrótów
public override int GetHashCode()
{
    unchecked
    {
        return (X * 397) ^ Y;
    }
}

public long MyHashFunction()
{
    return (X << 32) | Y;
}

public class LineToPointAdapter
    : IEnumerable<Point>
{
    static Dictionary<Line, List<Point>> cache
        = new Dictionary<Line, List<Point>>();
    private Line line;
    public LineToPointAdapter(Line line)
    {
        if (cache.ContainsKey(line)) return; // we already have it
        this.line = line;
        //tak, jak wcześniej

        cache.Add(line, points);
    }

    public IEnumerator<Point> GetEnumerator()
    {
        return cache[line].GetEnumerator();
    }
}

public class LineToPointAdapter : IEnumerable<Point>
{
    ...
    private void Prepare()
    {
        if (cache.ContainsKey(line)) return; // we already have it
        //reszta kodu tak, jak poprzednio
    }
    public IEnumerator<Point> GetEnumerator()
    {
        Prepare();
        return cache[line].GetEnumerator();
    }
}

//Adapter właściwości (surogat)
public Dictionary<string, string> Capitals { get; set; }
public (string, string)[] CapitalsSerializable
{
    get
    {
        return Capitals.Keys.Select(country =>
            (country, Capitals[country])).ToArray();
    }
    set
    {
        Capitals = value.ToDictionary(x => x.Item1, x => x.Item2);
    }
}

<?xml version="1.0" encoding="utf-16"?>
<CountryStats xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <CapitalsSerializable>
        <ValueTupleOfStringString>
            <Item1>Francja</Item1>
            <Item2>Paryż</Item2>
        </ValueTupleOfStringString>
    </CapitalsSerializable>
</CountryStats>
