import Foundation
import OrderedCollections


public struct ObjModel
{
	public var positions = [Vec3f]()
	public var colours = [Vec3f]()
	public var normals = [Vec3f]()
	public var texCoords = [Vec2f]()
	public var objects = OrderedDictionary<String, Object>()
	public var materials = Dictionary<String, ObjMaterial>()

	public struct Object { public var meshes = [Mesh]() }
	public struct Mesh { public var material = "", faces = [Face]() }
	public struct Index { public let p: Int, n: Int, t: Int }
	public enum Face
	{
		case line(p1: Int, p2: Int)
		case triangle(v1: Index, v2: Index, v3: Index)
		case quad(v1: Index, v2: Index, v3: Index, v4: Index)
		case ngon(_ v: [Index])
	}
}

public struct ObjMaterial
{
	var model: IlluminationModel = .lambert
	var ambient: Colour = .black
	var diffuse: Colour = .white
	var specular: Colour = .zero
	var specularExp: Float = 30
	var refraction: Float = 1.0
	var alpha: Float = 1.0

	var ambientMap: TextureMap? = nil
	var diffuseMap: TextureMap? = nil
	var specularMap: TextureMap? = nil
	var specularExpMap: TextureMap? = nil
	var dissolveMap: TextureMap? = nil
	var bumpMap: TextureMap? = nil
	var displacementMap: TextureMap? = nil
	var decalMap: TextureMap? = nil

	// PBR extensions
	var roughness: Float = 0.5
	var metallic: Float = 0.0
	var sheen: Float = 0.0
	var clearcoatThickness: Float = 0.0
	var clearcoatRoughness: Float = 0.03 // dunno what the default should be so here's the blender one
	var emmision: Vec3f = .zero
	var anisotropy: Float = 0.0
	var anisotropyRotation: Float = 0.0

	var roughnessMap: TextureMap? = nil
	var metallicMap: TextureMap? = nil
	var sheenMap: TextureMap? = nil
	var emmisionMap: TextureMap? = nil
	var normalMap: TextureMap? = nil

	// Ft  Fresnel reflectance
	// Ft  Fresnel transmittance
	// Ns  Specular exponent/shininess
	// Ia  Ambient light
	// I   Light intensity
	// Ir  Intensity from reflected direction (reflection map and/or raytracing)
	// It  Intensity from transmitted direction
	// Ka  Ambient reflectance
	// Kd  Diffuse reflectance
	// Ks  Specular reflectance
	// Tf  Transmission filter
	// H   Unit vector bisector between L and V
	// L   Unit light vector
	// N   Unit surface normal
	// V   Unit view vector
	//
	// j = piss
	// Fr = Fresnel
	// lambert = KaIa + Kd(N * Lj)Ij
	// blinnPhong = lambert + Ks((H * Hj) ^ Ns)Ij
	//
	// Notes: Clara.io only usees 0 and 1, maya always exports 4, blender can export 1, 3, 6
	public enum IlluminationModel
	{
		case colour  // Kd only
		case lambert
		case blinnPhong
		case reflectionRaytrace                             // + Ir = (intensity of reflection map) + (ray trace)
		case transparentGlassReflectionRaytrace             // + Ir = (glass) + (intensity of reflection map) + (raytrace)
		case reflectionRaytraceFresnel                      // Fr(Lj * Hj, Ks, Ns)Ij + Fr(N * V, Ks, Ns)Ir
		case transparentRefractionReflectionRaytrace        // + Ir + (1 - Ks)TfIt
		case transparentRefractionReflectionRaytraceFresnel // Fr(Lj * Hj, Ks, Ns)Ij + Fr(N * V, Ks, Ns)Ir
		case reflection                                     // + Ir = (intensity of reflection map)
		case transparentGlassReflection                     // + Ir = (glass) + (intensity of reflection map) + (raytrace)
		case shadowOnly
	}

	public struct TextureMap
	{
		var path: String = .init()
		var flags: Flags = [ .blendHoriz, .blendVert ]
		var blendMul: Float = 1.0
		var boost: Float = 0.0  // non-negative
		var imfChan: ImfChan = .l  // decal ? .m : .l
		var mmBaseGain: (Float, Float) = (0.0, 1.0)
		var offset: Vec3f = .zero
		var scale: Vec3f = .one
		var turbulence: Vec3f = .zero
		var textureResolution: Int = 0

		struct Flags: OptionSet
		{
			let rawValue: UInt8

			static let blendHoriz: Self       = Self(rawValue: 1 << 0)
			static let blendVert: Self        = Self(rawValue: 1 << 1)
			static let colourCorrection: Self = Self(rawValue: 1 << 2)
			static let clamp: Self            = Self(rawValue: 1 << 3)
		}

		enum ImfChan { case r, g, b, m, l, z }
	}
}
