# The mighty [class] macro implements OOP operations
define-macro class : syntax-rules
	`[class @typename @::_classBodyLines] [atom typename] : begin
		local bodylines : _classBodyLines.map formOf
		local Inherits null

		local terms {}
		local accessorAssignments {.}
		
		define [wrapPublicMethodBody name body] `[begin \\
			define-macro super : syntax-rules
				`[super @::args] `[(Super.prototype.(@2*{".quote" name})).call @[dirty `this] @::args]
			* @body
		]
		define [wrapPublicAccrssorBody name body] `[begin \\
			define-macro super : syntax-rules
				`[super] : let [t : env.newt] `[begin \\
					set t Super.prototype
					while (t && ![@[externEnv.use 'Object'].getOwnPropertyDescriptor t @2*{".quote" name}]) : set t [@[externEnv.use 'Object'].getPrototypeOf t]
					[@[externEnv.use 'Object'].getOwnPropertyDescriptor t @2*{".quote" name}].get.call @[dirty `this]
				]
				`[super @value] : let [t : env.newt] `[begin \\
					set t Super.prototype
					while (t && ![@[externEnv.use 'Object'].getOwnPropertyDescriptor t @2*{".quote" name}]) : set t [@[externEnv.use 'Object'].getPrototypeOf t]
					[@[externEnv.use 'Object'].getOwnPropertyDescriptor t @2*{".quote" name}].set.call @[dirty `this] @value
				]
			* @body
		]
		define [wrapProtectedMethodBody name body] `[begin \\
			define-macro super : syntax-rules
				`[super @::args] `[(Super.prototype.(Super.protectedTerms.(@2*{".quote" name}))).call @[dirty `this] @::args]
			* @body
		]
		
		define [ConstructorTerm params body] : lambda `[set Constructor : function @params : begin \\
			define-macro super : syntax-rules
				`[super @::args] `[Super.call @[dirty `this] @::args]
			* @body
			return nothing
		]
		define [PublicTerm name val] : lambda [] `[set Type.prototype.(@{".quote" name}) @val]
		define [PublicAccessorTerm kind name val] : begin
			local r : lambda [] `nothing
			if [not accessorAssignments.(name)] : then
				set f {
					.get [if externEnv.strict `[lambda [] : throw "The property \(@{'.quote' name}) is write-only."] `nothing]
					.set [if externEnv.strict `[lambda [] : throw "The property \(@{'.quote' name}) is read-only."] `nothing]
				}
				set accessorAssignments.(name) f
				set r : lambda [] `[@[externEnv.use 'Object'].defineProperty Type.prototype @{".quote" name} {
					.get @(accessorAssignments.(name).get)
					.set @(accessorAssignments.(name).set)
				}]
			set accessorAssignments.(name).(kind) val
			return r
		define [PrivateTerm name val] : lambda [] `[begin \\
			local @('__private_' + name) [Symbol]
			set Type.prototype.(@('__private_' + name)) @val
		]
		define [ProtectedTerm name val] : lambda [] `[begin \\
			if [not protectedTerms.(@{".quote" name})] : set protectedTerms.(@{".quote" name}) [Symbol]
			set Type.prototype.(protectedTerms.(@{".quote" name})) @val
		]
		define [StaticTerm name val] : lambda [] `[set Type.(@{".quote" name}) @val]
		
		define [handleTerm term] : match term
			# Inheritance term
			`[inherits @Super @::indentedLines] : begin
				set Inherits Super
				indentedLines.forEach handleTerm # for `[class Type : inherits Base<newline><indent>whatever] form
			
			# Public term
			# constructor
			`[define public [new @::params] @body] : terms.push : ConstructorTerm params body
			# property
			`[define public get @name @body] [atom name] : terms.push : PublicAccessorTerm 'get' name `[lambda [] @[wrapPublicAccrssorBody name body]]
			`[define public get @name] [atom name] : handleTerm `[define public get @name [private @name]]
			`[define public set @name @value @body] [atom name] : terms.push : PublicAccessorTerm 'set' name `[lambda [@value] @[wrapPublicAccrssorBody name body]]
			`[define public set @name] [atom name] : handleTerm `[define public set @name value : set [private @name] value]

			`[define public @modifer get @name @body] [atom name] : begin
				if [not accessorAssignments.(name)] : set accessorAssignments.(name) {.get `nothing .set `nothing}
				set accessorAssignments.(name).get `[@modifer : lambda [] @[wrapPublicAccrssorBody name body]]
			`[define public @modifer get @name] [atom name] : handleTerm `[define public @modifer get @name [private @name]]
			`[define public @modifer set @name @value @body] [atom name] : begin
				if [not accessorAssignments.(name)] : set accessorAssignments.(name) {.get `nothing .set `nothing}
				set accessorAssignments.(name).set `[@modifer : lambda [@value] @[wrapPublicAccrssorBody name body]]
			`[define public @modifer set @name] [atom name] : handleTerm `[define public @modifer set @name value : set [private @name] value]
			# method
			`[define public [@name @::params] @body] [atom name] : begin
				terms.push : PublicTerm name `[lambda @params @[wrapPublicMethodBody name body]]
			`[define public @modifer [@name @::params] @body] [atom name] : begin
				terms.push : PublicTerm name `[@modifer : lambda @params @[wrapPublicMethodBody name body]]
			# proto value
			`[define public @name @val] [atom name] : begin
				terms.push : PublicTerm name val

			# Private term, use symbols
			`[define private @name] [atom name] : terms.push : PrivateTerm name `nothing
			`[define private @name @val] [atom name] : terms.push : PrivateTerm name val
			`[define private [@name @::params] @body] [atom name] : terms.push : PrivateTerm name `[function @params @body]
			`[define private @modifer [@name @::params] @body] [atom name] : terms.push : PrivateTerm name `[@modifer : function @params @body]
			
			# Protected term, use symbols
			`[define protected @name] [atom name] : terms.push : ProtectedTerm name `nothing
			`[define protected @name @val] [atom name] : terms.push : ProtectedTerm name val
			`[define protected [@name @::params] @body] [atom name] : terms.push : ProtectedTerm name `[function @params @[wrapProtectedMethodBody name body]]
			`[define protected @modifer [@name @::params] @body] [atom name] : terms.push : ProtectedTerm name `[@modifer : function @params @[wrapProtectedMethodBody name body]]
			
			# Static term
			`[define static @name] [atom name] : terms.push : StaticTerm name `nothing
			`[define static @name @val] [atom name] : terms.push : StaticTerm name val
			`[define static [@name @::params] @body] [atom name] : terms.push : StaticTerm name `[function @params @body]
			`[define static @modifer [@name @::params] @body] [atom name] : terms.push : StaticTerm name `[@modifer : function @params @body]
			
			# Shorthands
			`[public @::whatever] : handleTerm `[define public @::whatever]
			`[private @::whatever] : handleTerm `[define private @::whatever]
			`[protected @::whatever] : handleTerm `[define protected @::whatever]
			`[static @::whatever] : handleTerm `[define static @::whatever]
			
			# Modifer applications
			`[apply @mdf] : terms.push : lambda [] `[@mdf.call Type.prototype Type Super]
			
			otherwise : the-form-is-invalid

		foreach [bl bodylines] : handleTerm bl
		
		return : dirty `[begin \\
			define @typename : [function [Super] : begin \\
				# constructor
				local Constructor : lambda
				local Type [function [] : begin [Constructor.apply this arguments] [return nothing]]
				
				# inheritance
				* @[if Inherits `[set Type.prototype [@[externEnv.use 'Object'].create Super.prototype]] `nothing]
				
				extern Symbol
				[lambda [] : begin \\
					define-macro public : begin
						local f : syntax-rules
							`[public @name] [atom name] : dirty `(this.(@{".quote" [formOf name]}))
						set coinit.initFn : lambda [m] : begin
							set m.toPattern : lambda [form env w] : match form
								`[public @name] [atom name] : object
									whether : lambda [t] `true
									assign : lambda [t locally] : ex `[set this.(@{".quote" name}) @t] env
						*f
					define-macro private : begin
						local f : syntax-rules
							`[private @name] [atom name] : dirty `(this.(@[definingEnv.use ("__private_" + [formOf name])]))
						set coinit.initFn : lambda [m] : begin
							set m.toPattern : lambda [form env w] : match form
								`[private @name] [atom name] : object
									whether : lambda [t] `true
									assign : lambda [t locally] : ex `[set this.(@[definingEnv.use ("__private_" + name)]) @t] env
						*f
					define-macro protected : begin
						local f : syntax-rules
							`[protected @name] [atom name] : dirty `(this.(@[definingEnv.use 'protectedTerms'].(@{".quote" [formOf name]})))
						set coinit.initFn : lambda [m] : begin
							set m.toPattern : lambda [form env w] : match form
								`[protected @name] [atom name] : object
									whether : lambda [t] `true
									assign : lambda [t locally] : ex `[set (this.(@[definingEnv.use 'protectedTerms'].(@{".quote" name}))) @t] env
						*f
					set Type.protectedTerms : local protectedTerms : @[externEnv.use 'Object'].create (Super.protectedTerms || null)

					begin @::[terms.map : [t] -> [t]]
					
					return nothing
				].call Type.prototype
				return Type
			] @[if Inherits Inherits [externEnv.use 'Object']]
		]
	`[class [@typename @::_params] @::_terms] : begin
		local params : _params.map formOf
		local terms : _terms.map formOf
		local arity {'.quote' params.length}
		return : dirty `[define @typename : [function [] [begin \\
			extern Map
			local cache : new Map
			define [GenericType @::params] : begin
				local pivot cache
				for [local j 0] (j < @arity) [inc j] : begin
					if [pivot.has arguments.(j)] : then
						set pivot [pivot.get arguments.(j)]
					: else
						if (j < @arity - 1) : then
							local m [new Map]
						: else
							local m : [lambda [@::params] [class _r @::terms]].apply null arguments
						
						pivot.set arguments.(j) m
						set pivot m
				return pivot
			return GenericType
		]]]
	
	otherwise : the-form-is-invalid


class Shape
	public get area : return nothing
	static kind "Shape"

class Rectangle : inherits Shape
	private width 0
	private height 0
	
	public get width
	public get height
	
	public [new width height] : begin
		set [private width] width
		set [private height] height
	
	public get area : [private width] * [private height]
	static kind "Rectangle"

class Square : inherits Rectangle
	private side 0
	public [new side] : begin
		super side side
		set [private side] side
	static kind "Square"

local cell : new Square 1
console.log cell.area
console.log cell.height

# Generic type
#class [Either T]
#	public [new x]
#		nothing
#
#console.log : [Either Number] == [Either Number]