using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Threading.Tasks;
namespace Amphora.RandomComparisons
{
///
/// Draws the picture that made RANDU famous. Consecutive draws are taken three at a time as points in a cube. A
/// generator that puts them on a few planes shows as stripes through a thin slice of the cube, where a good one
/// shows as noise.
///
public static class Hyperplanes
{
public const int PanelSize = 480;
public const int TitleHeight = 24;
public const int Margin = 8;
public const int Columns = 5;
public const int SliceBits = 10;
public const int CoordinateBits = 24;
///
/// Counts the points of a thin slice of the cube, by their other two coordinates. Triples overlap, each draw
/// being the end of one triple and the start of the next.
///
public static int[] Slice(Subject subject, ulong seed, long draws)
{
var counts = new int[PanelSize * PanelSize];
var stream = new WordStream(subject.Make(seed));
int first = (int)Windows.Take(stream.Next(), Window.High, CoordinateBits);
int second = (int)Windows.Take(stream.Next(), Window.High, CoordinateBits);
int sliceStart = 1 << (CoordinateBits - 1);
int sliceEnd = sliceStart + (1 << (CoordinateBits - SliceBits));
for (long i = 2; i < draws; i++)
{
int third = (int)Windows.Take(stream.Next(), Window.High, CoordinateBits);
if (third >= sliceStart && third < sliceEnd)
{
int x = (int)((long)first * PanelSize >> CoordinateBits);
int y = (int)((long)second * PanelSize >> CoordinateBits);
counts[y * PanelSize + x]++;
}
first = second;
second = third;
}
return counts;
}
public static void Write(string path, IReadOnlyList subjects, ulong seed, Scale scale)
{
long draws = scale.Count(1L << 27, minimum: 1L << 22);
var slices = new int[subjects.Count][];
Parallel.For(0, subjects.Count, index => slices[index] = Slice(subjects[index], seed, draws));
int rows = (subjects.Count + Columns - 1) / Columns;
int cellWidth = PanelSize + Margin;
int cellHeight = PanelSize + TitleHeight + Margin;
using (var bitmap = new Bitmap(Columns * cellWidth + Margin, rows * cellHeight + Margin))
using (Graphics graphics = Graphics.FromImage(bitmap))
using (var font = new Font("Arial", 11))
{
graphics.Clear(Color.FromArgb(24, 24, 28));
for (int index = 0; index < subjects.Count; index++)
{
int left = Margin + index % Columns * cellWidth;
int top = Margin + index / Columns * cellHeight;
graphics.DrawString(subjects[index].Name + (subjects[index].IsControl ? " (control)" : ""), font,
Brushes.White, left, top);
graphics.FillRectangle(Brushes.Black, left, top + TitleHeight, PanelSize, PanelSize);
for (int y = 0; y < PanelSize; y++)
{
for (int x = 0; x < PanelSize; x++)
{
if (slices[index][y * PanelSize + x] > 0)
{
bitmap.SetPixel(left + x, top + TitleHeight + y, Color.White);
}
}
}
}
bitmap.Save(path, ImageFormat.Png);
}
}
}
}