using System;
using System.ComponentModel.DataAnnotations;
namespace NHSE.Core;
public sealed record MapViewState
{
private const int MaxX = LayerFieldItem.TilesPerAcreDim * AcreCoordinate.CountAcreExteriorWidth;
private const int MaxY = LayerFieldItem.TilesPerAcreDim * AcreCoordinate.CountAcreExteriorHeight;
private const int EdgeBuffer = LayerFieldItem.TilesPerAcreDim;
///
/// Amount the X/Y coordinates change when using arrow movement.
///
[Range(1, 8)] public uint ArrowViewInterval { get; set; } = 2;
[Range(0, 1)] public int ItemLayerIndex
{
get;
set
{
if (value is not (0 or 1))
throw new ArgumentOutOfRangeException(nameof(value), "Item layer index must be 0 or 1.");
field = value;
}
}
///
/// Top-left-origin X coordinate of the view.
///
public int X
{
get;
private set
{
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)value, (uint)MaxX);
field = value;
}
}
///
/// Top-left-origin Y coordinate of the view.
///
public int Y
{
get;
private set
{
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)value, (uint)MaxY);
field = value;
}
}
public bool CanUp => Y != 0;
public bool CanDown => Y != MaxY;
public bool CanLeft => X != 0;
public bool CanRight => X != MaxX;
///
/// Moves the view up by tiles.
///
/// if the view changed; otherwise, .
public bool ArrowUp()
{
if (!CanUp)
return false;
Y = Math.Max(0, Y - (int)ArrowViewInterval);
return true;
}
///
/// Moves the view left by tiles.
///
/// if the view changed; otherwise, .
public bool ArrowLeft()
{
if (!CanLeft)
return false;
X = Math.Max(0, X - (int)ArrowViewInterval);
return true;
}
///
/// Moves the view right by tiles.
///
/// if the view changed; otherwise, .
public bool ArrowRight()
{
if (!CanRight)
return false;
X = Math.Min(MaxX - EdgeBuffer, X + (int)ArrowViewInterval);
return true;
}
///
/// Moves the view down by tiles.
///
/// if the view changed; otherwise, .
public bool ArrowDown()
{
if (!CanDown)
return false;
Y = Math.Min(MaxY - EdgeBuffer, Y + (int)ArrowViewInterval);
return true;
}
///
/// Applies the requested coordinates (sanity checked).
///
/// if the view changed; otherwise, .
public bool SetViewTo(in int absX, in int absY)
{
var (x, y) = (X, Y);
X = Math.Clamp(absX, 0, MaxX - EdgeBuffer);
Y = Math.Clamp(absY, 0, MaxY - EdgeBuffer);
return x != X || y != Y;
}
///
/// Drags the view by the specified delta amounts.
///
/// if the view changed; otherwise, .
public bool DragView(int dX, int dY) => SetViewTo(X + dX, Y + dY);
///
/// Sets the view to the top-left of the specified acre.
///
///
/// Acres are ordered Y-down-first.
///
/// Acre index to set the view to.
public void SetViewToAcre(int acre)
{
var acreX = acre % (MaxX / EdgeBuffer);
var acreY = acre / (MaxX / EdgeBuffer);
SetViewTo(acreX * EdgeBuffer, acreY * EdgeBuffer);
}
public (int X, int Y) EnforceEdgeBuffer(int x, int y)
{
x = Math.Clamp(x, 0, MaxX - EdgeBuffer);
y = Math.Clamp(y, 0, MaxY - EdgeBuffer);
return (x, y);
}
public bool IsWithinView(int x, int y, int tileStride)
{
if (x < X || x >= X + tileStride)
return false;
if (y < Y || y >= Y + tileStride)
return false;
return true;
}
}