# Rotates a Windows display via Win32 ChangeDisplaySettingsEx.
# Usage:
#   .\orientation-script.ps1 <DisplayId> <Rotation>
#   .\orientation-script.ps1              # reset displays 0..9 to 0°
# DisplayId: 0 = DISPLAY1, 1 = DISPLAY2, ...
# Rotation:  0 | 90 | 180 | 270 (clockwise)

param(
    [Parameter(Position = 0)]
    [int]$DisplayId,

    [Parameter(Position = 1)]
    [ValidateSet(0, 90, 180, 270)]
    [int]$Rotation
)

if (-not ('EmmaDisplay.Rotate' -as [type])) {
    Add-Type -ReferencedAssemblies System -Language CSharp -TypeDefinition @'
using System;
using System.Runtime.InteropServices;

namespace EmmaDisplay
{
    public static class Rotate
    {
        const int ENUM_CURRENT_SETTINGS = -1;
        const int CDS_UPDATEREGISTRY = 1;
        const int DMDO_DEFAULT = 0;
        const int DMDO_90 = 3;
        const int DMDO_180 = 2;
        const int DMDO_270 = 1;

        public static void Apply(uint deviceId, int degrees)
        {
            var device = new DISPLAY_DEVICE();
            device.cb = Marshal.SizeOf(device);

            if (!EnumDisplayDevices(null, deviceId, ref device, 0))
                throw new InvalidOperationException("Display " + deviceId + " not found.");

            var mode = new DEVMODE();
            mode.dmSize = (short)Marshal.SizeOf(mode);

            if (EnumDisplaySettings(device.DeviceName, ENUM_CURRENT_SETTINGS, ref mode) == 0)
                throw new InvalidOperationException("Cannot read settings for " + device.DeviceName + ".");

            int target = DegreesToDmdo(degrees);
            bool currentPortrait = IsPortrait(mode.dmDisplayOrientation);
            bool targetPortrait = IsPortrait(target);

            if (currentPortrait != targetPortrait)
            {
                int temp = mode.dmPelsHeight;
                mode.dmPelsHeight = mode.dmPelsWidth;
                mode.dmPelsWidth = temp;
            }

            mode.dmDisplayOrientation = target;

            int result = ChangeDisplaySettingsEx(
                device.DeviceName, ref mode, IntPtr.Zero, CDS_UPDATEREGISTRY, IntPtr.Zero);

            if (result != 0)
                throw new InvalidOperationException(
                    "ChangeDisplaySettingsEx failed for " + device.DeviceName +
                    " (code " + result + ").");
        }

        static int DegreesToDmdo(int degrees)
        {
            switch (degrees)
            {
                case 90:  return DMDO_90;
                case 180: return DMDO_180;
                case 270: return DMDO_270;
                default:  return DMDO_DEFAULT;
            }
        }

        static bool IsPortrait(int dmdo)
        {
            return dmdo == DMDO_90 || dmdo == DMDO_270;
        }

        [DllImport("user32.dll")]
        static extern int ChangeDisplaySettingsEx(
            string lpszDeviceName, ref DEVMODE lpDevMode, IntPtr hwnd, int dwflags, IntPtr lParam);

        [DllImport("user32.dll")]
        static extern bool EnumDisplayDevices(
            string lpDevice, uint iDevNum, ref DISPLAY_DEVICE lpDisplayDevice, uint dwFlags);

        [DllImport("user32.dll", CharSet = CharSet.Ansi)]
        static extern int EnumDisplaySettings(
            string lpszDeviceName, int iModeNum, ref DEVMODE lpDevMode);

        [StructLayout(LayoutKind.Explicit, CharSet = CharSet.Ansi)]
        struct DEVMODE
        {
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
            [FieldOffset(0)]  public string dmDeviceName;
            [FieldOffset(32)] public short dmSpecVersion;
            [FieldOffset(34)] public short dmDriverVersion;
            [FieldOffset(36)] public short dmSize;
            [FieldOffset(38)] public short dmDriverExtra;
            [FieldOffset(40)] public int dmFields;
            [FieldOffset(44)] public int dmPositionX;
            [FieldOffset(48)] public int dmPositionY;
            [FieldOffset(52)] public int dmDisplayOrientation;
            [FieldOffset(56)] public int dmDisplayFixedOutput;
            [FieldOffset(60)] public short dmColor;
            [FieldOffset(62)] public short dmDuplex;
            [FieldOffset(64)] public short dmYResolution;
            [FieldOffset(66)] public short dmTTOption;
            [FieldOffset(68)] public short dmCollate;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
            [FieldOffset(72)] public string dmFormName;
            [FieldOffset(102)] public short dmLogPixels;
            [FieldOffset(104)] public int dmBitsPerPel;
            [FieldOffset(108)] public int dmPelsWidth;
            [FieldOffset(112)] public int dmPelsHeight;
            [FieldOffset(116)] public int dmDisplayFlags;
            [FieldOffset(120)] public int dmDisplayFrequency;
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
        struct DISPLAY_DEVICE
        {
            public int cb;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
            public string DeviceName;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
            public string DeviceString;
            public int StateFlags;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
            public string DeviceID;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
            public string DeviceKey;
        }
    }
}
'@
}

function Set-DisplayRotation {
    param(
        [Parameter(Mandatory)]
        [int]$Id,

        [Parameter(Mandatory)]
        [ValidateSet(0, 90, 180, 270)]
        [int]$Degrees
    )

    try {
        [EmmaDisplay.Rotate]::Apply([uint32]$Id, $Degrees)
        Write-Host "Display $Id -> ${Degrees}°"
    }
    catch {
        Write-Error "Failed to rotate display $Id to ${Degrees}°: $_"
    }
}

$hasDisplayId = $PSBoundParameters.ContainsKey('DisplayId')
$hasRotation = $PSBoundParameters.ContainsKey('Rotation')

if ($hasDisplayId -and $hasRotation) {
    Set-DisplayRotation -Id $DisplayId -Degrees $Rotation
}
elseif (-not $hasDisplayId -and -not $hasRotation) {
    0..9 | ForEach-Object { Set-DisplayRotation -Id $_ -Degrees 0 }
}
else {
    Write-Error 'Usage: orientation-script.ps1 [<DisplayId> <Rotation>]'
    exit 1
}
