import Foundation


struct DDSLoader: LoaderProtocol
{
	typealias T = Image

	func load(url: URL) -> T?
	{
		guard let image = try? DDSLoader.read(url: url) else { return nil }
		return image
	}

	func load(url: URL, content: inout ContentManager) -> T? { return load(url: url) }

	static func read(url: URL) throws -> T
	{
		let file = try FileHandle(forReadingFrom: url)
		let header = DDSHeader(
			magic:     .init(try file.read(upToCount: 4)),
			size:      try file.read(as: UInt32.self).littleEndian,
			flags:     .init(rawValue: try file.read(as: UInt32.self).littleEndian),
			height:    try file.read(as: UInt32.self).littleEndian,
			width:     try file.read(as: UInt32.self).littleEndian,
			pitch:     try file.read(as: UInt32.self).littleEndian,
			depth:     try file.read(as: UInt32.self).littleEndian,
			mipNum:    try file.read(as: UInt32.self).littleEndian,
			reserved1: try (0..<11).map { _ in try file.read(as: UInt32.self) },
			pixFmt: DDSPixelFormat(
				size:   try file.read(as: UInt32.self).littleEndian,
				flags:  .init(rawValue: try file.read(as: UInt32.self).littleEndian),
				fourCC: .init(try file.read(upToCount: 4)),
				bits:   try file.read(as: UInt32.self).littleEndian,
				rMask:  try file.read(as: UInt32.self).littleEndian,
				gMask:  try file.read(as: UInt32.self).littleEndian,
				bMask:  try file.read(as: UInt32.self).littleEndian,
				aMask:  try file.read(as: UInt32.self).littleEndian),
			caps1:     try file.read(as: UInt32.self).littleEndian,
			caps2:     try file.read(as: UInt32.self).littleEndian,
			caps3:     try file.read(as: UInt32.self).littleEndian,
			caps4:     try file.read(as: UInt32.self).littleEndian,
			reserved2: try file.read(as: UInt32.self))

		guard header.magic == FourCC("DDS "),
			header.size == 124 // MemoryLayout<DDSHeader>.size
		else { throw NSError() /* ("DDS Header size mismatch") */ }

		//#if MemoryLayout<DDSHeaderDX10>.size != 20
//#error("DDS DX10 Extended Header size mismatch")
//#endif

		let format = try Image.Format.resolve(pixFmt: header.pixFmt)
		let size = (0...header.mipNum).reduce(0) { size, i in
			let width = header.width &>> i, height = header.height &>> i
			guard width > 0 && height > 0 else { return size }
			return size + format.computeSize(width: width, height: height)
		}

		guard let data = try file.read(upToCount: size)
		else { throw NSError() }

		return .init(data,
			format: format,
			width: Int(header.width), height: Int(header.height),
			mipLevels: Int(header.mipNum))
	}
}

fileprivate extension Image.Format
{
	static func resolve(pixFmt: DDSPixelFormat) throws -> Self
	{
		switch pixFmt
		{
		case DDSPixelFormat(.rgb, bits: 24, r: 0xFF0000, g: 0x00FF00, b: 0x0000FF): .rgb888
		case DDSPixelFormat(.rgb, bits: 24, r: 0x0000FF, g: 0x00FF00, b: 0xFF0000): .bgr888
		case DDSPixelFormat(.rgba, bits: 32, r: 0x00FF0000, g: 0x0000FF00, b: 0x000000FF, a: 0xFF000000): .argb8888
		case DDSPixelFormat(.rgba, bits: 32, r: 0x000000FF, g: 0x0000FF00, b: 0x00FF0000, a: 0xFF000000): .abgr8888
		//case DDSPixelFormat(.luminance, bits: 8, r: 0xFF): .l8
		//case DDSPixelFormat(.alpha,     bits: 8, a: 0xFF): .a8
		//case DDSPixelFormat(.luminance,  bits: 16, a: 0xFFFF): .l16
		//case DDSPixelFormat(.luminanceA, bits: 16, r: 0x00FF, a: 0xFF00): .al88
		case DDSPixelFormat(.fourCC, fourCC: .init("DXT1")): .s3tc_bc1
		case DDSPixelFormat(.fourCC, fourCC: .init("DXT2")): .s3tc_bc2_premul
		case DDSPixelFormat(.fourCC, fourCC: .init("DXT3")): .s3tc_bc2
		case DDSPixelFormat(.fourCC, fourCC: .init("DXT4")): .s3tc_bc3_premul
		case DDSPixelFormat(.fourCC, fourCC: .init("DXT5")): .s3tc_bc3
		case DDSPixelFormat(.fourCC, fourCC: .init("ATI1")): .rgtc_bc4
		case DDSPixelFormat(.fourCC, fourCC: .init("ATI2")): .rgtc_bc5_3dc
		default: throw NSError()
		}
	}
}

internal extension Image.Format
{
	private func dxtcSize(_ w: UInt32, _ h: UInt32) -> Int
	{
		let mul = [.s3tc_bc1, .rgtc_bc4].contains(self) ? 8 : 16
		let cw = Int(w + 3) / 4
		let ch = Int(h + 3) / 4
		return cw * ch * mul
	}

	func computeSize(width w: UInt32, height h: UInt32) -> Int
	{
		switch self
		{
		//case .l8, .a8:             Int(w) * Int(h)
		//case .l16, .al88:          Int(w) * Int(h) * 2
		case .rgb888, .bgr888:     Int(w) * Int(h) * 3
		case .argb8888, .abgr8888: Int(w) * Int(h) * 4
		case .s3tc_bc1, .s3tc_bc2_premul, .s3tc_bc2, .s3tc_bc3_premul, .s3tc_bc3, .rgtc_bc4, .rgtc_bc5_3dc:
			dxtcSize(w, h)
		}
	}
}

struct DDSPixelFormat: Equatable
{
	let size: UInt32
	let flags: Flags
	let fourCC: FourCC
	let bits: UInt32
	let rMask: UInt32, gMask: UInt32, bMask: UInt32, aMask: UInt32

	struct Flags: OptionSet
	{
		let rawValue: UInt32

		static let alphaPix   = Self(rawValue: 0x00000001)
		static let alpha      = Self(rawValue: 0x00000002)
		static let fourCC     = Self(rawValue: 0x00000004)
		static let rgb        = Self(rawValue: 0x00000040)
		static let yuv        = Self(rawValue: 0x00000200)
		static let indexed1   = Self(rawValue: 0x00000800)
		static let indexed2   = Self(rawValue: 0x00001000)
		static let indexed4   = Self(rawValue: 0x00000008)
		static let indexed8   = Self(rawValue: 0x00000020)
		static let luminance  = Self(rawValue: 0x00020000)
		static let premult    = Self(rawValue: 0x00008000)

		static let nvNormal   = Self(rawValue: 0x80000000)
		static let nvSrgb     = Self(rawValue: 0x40000000)

		static let rgba: Self       = [ .rgb, .alphaPix ]
		static let luminanceA: Self = [ .luminance, .alphaPix ]
	}
}

extension DDSPixelFormat
{
	init(
		_ flags: Flags,
		fourCC: FourCC = .init(),
		bits: UInt32 = 0,
		r: UInt32 = 0, g: UInt32 = 0, b: UInt32 = 0, a: UInt32 = 0)
	{
		self.size = UInt32(MemoryLayout<DDSPixelFormat>.size)
		assert(self.size == 32, "DDS pixel format size mismatch")
		self.flags = flags
		self.fourCC = fourCC
		self.bits = bits
		self.rMask = r
		self.gMask = g
		self.bMask = b
		self.aMask = a
	}
}

struct FourCC: Equatable
{
	let rawValue: UInt32

	init()
	{
		self.rawValue = 0
	}

	init(rawValue: UInt32)
	{
		self.rawValue = rawValue.bigEndian
	}

	init(_ data: Data?)
	{
		assert(data != nil)
		assert(data!.count == 4)
		self.rawValue = data!.withUnsafeBytes { $0.load(as: UInt32.self).bigEndian }
	}

	init(_ a: Character, _ b: Character, _ c: Character, _ d: Character)
	{
		assert(a.isASCII && b.isASCII && c.isASCII && d.isASCII)
		var u = UInt32(a.asciiValue!)
		u |= UInt32(b.asciiValue!) << 8
		u |= UInt32(c.asciiValue!) << 16
		u |= UInt32(d.asciiValue!) << 24
		self.rawValue = u.bigEndian
	}

	init(_ abcd: String)
	{
		assert(abcd.count == 4)
		self.init(
			abcd[abcd.index(abcd.startIndex, offsetBy: 0)],
			abcd[abcd.index(abcd.startIndex, offsetBy: 1)],
			abcd[abcd.index(abcd.startIndex, offsetBy: 2)],
			abcd[abcd.index(abcd.startIndex, offsetBy: 3)])
	}
}

struct DDSHeader
{
	let magic: FourCC
	let size: UInt32
	let flags: Flags
	let height: UInt32, width: UInt32, pitch: UInt32, depth: UInt32
	let mipNum: UInt32
	let reserved1: [UInt32]
	let pixFmt: DDSPixelFormat
	let caps1: UInt32, caps2: UInt32, caps3: UInt32, caps4: UInt32
	let reserved2: UInt32

	struct Flags: OptionSet
	{
		let rawValue: UInt32

		static let caps   = Self(rawValue: 0x000001)
		static let height = Self(rawValue: 0x000002)
		static let width  = Self(rawValue: 0x000004)
		static let pitch  = Self(rawValue: 0x000008)
		static let pixFmt = Self(rawValue: 0x001000)
		static let mipNum = Self(rawValue: 0x020000)
		static let linSz  = Self(rawValue: 0x080000)
		static let depth  = Self(rawValue: 0x800000)
	}
}

