// grid3.js - module to insert a manual chords
//
// Copyright (C) 2020-2025 Jean-Francois Moine
//
// This file is part of abc2svg.
//
// abc2svg is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// abc2svg is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with abc2svg.  If not, see <http://www.gnu.org/licenses/>.
//
// This module is loaded when "%%begingrid" appears in a ABC source.
//
// Parameters
//	%%begingrid [ chord-define [ noprint ] ] [ lm=<left margin> ] [ width=<width> ]
//	    list of chords, '-' or '.', measure bars ('|') and ':' for repeat
//	%%endgrid
//
//	%%gridfont font_name size (default: 'serif 16')
//
// When this command appears inside a tune and when 'chord-define' is present,
// the chords are used to define the chord symbols that are displayed
// above the staff system.
// When 'noprint' is also present, the grid itself is not displayed.
//
// The left margin may be '-1' to center the grid (default).

if (typeof abc2svg == "undefined")
    var	abc2svg = {}

abc2svg.grid3 = {

// generate the grid
    block_gen: function(of, s) {
	if (s.subtype != "grid3") {
		of(s)
		return
	}

	this.set_page()
	this.blk_flush()

    var	abc = this,
	cfmt = abc.cfmt(),
	img = abc.get_img(),
	posy = abc.get_posy(),
	txt = s.text,
	font, font_cl, cls, w,
	ln, i,
	lines = [],
	cl = [],
	bars = [],
	cells = [],
	nr = 0,			// number of rows
	nc = 0,			// number of columns
	wc = 0			// width of a cell

	// generate the grid
	function build_grid() {
	    var	i, k, l, line, bl, bar, w, hr, x0, x, y, yl, cl, cell,
		lc = '',
		path = '<path class="stroke" stroke-width="1" d="M',
		sf = '" style="font-size:' + (font.size * .72).toFixed(1) + 'px'
						// small font

		function build_ch(cl, x, y, n) {
			return '<text class="' + cl + '" x="' +
				x.toFixed(1) + '" y="' + y.toFixed(1) + '">' +
					cell[n] + '</text>\n'
		} // build_ch()

		function build_cell(cell, x, y, yl, hr) {
		    var	line

			if (cell.length > 1) {
				line = path +
					(x - wc / 2).toFixed(1) + ' ' +
					yl.toFixed(1) + 'l' +
					wc.toFixed(1) + ' -' + hr.toFixed(1) +
					'"/>\n'
				if (cell[1]) {
				    line += path +
					(x - wc / 2).toFixed(1) + ' ' +
					(yl - hr).toFixed(1) + 'l' +
					(wc / 2).toFixed(1) + ' ' + (hr / 2).toFixed(1) +
						'"/>\n' +
					build_ch(cls + sf, x - wc / 3, y, 0) +
					build_ch(cls + sf, x, y - hr * .32, 1)
				} else {
					line += build_ch(cls + sf,
						x - wc * .2, y - hr / 4, 0)
				}
				if (cell.length >= 3) {
				  if (cell[3]) {
				    line += path +
					x.toFixed(1) + ' ' +
					(yl - hr / 2).toFixed(1) + 'l' +
					(wc / 2).toFixed(1) + ' ' + (hr / 2).toFixed(1) +
						'"/>\n' +
					build_ch(cls + sf, x, y + hr * .3, 2) +
					build_ch(cls + sf, x + wc / 3, y, 3)
				  } else {
					line += build_ch(cls + sf,
						x + wc * .2, y + hr / 4, 2)
				  }
				}
			} else {
				line = build_ch(cls, x, y, 0)
			}
			return line
		} // build_cell()

		// -- build_grid() --

		// build the content of the cells
		hr = font.size * 2.1
		if (wc < hr * 1.4)
			wc = hr * 1.4			// cell width
		if (s.width) {
			w = s.width
			wc = w / nc
		} else {
			w = wc * nc			// grid width
		}
		x0 = s.lm < 0
			? (img.width / cfmt.scale - w) / 2	// center
			: s.lm + 1
		if ((s.fmt || cfmt).trimsvg)		// adjust the SVG width
			img.wx = x0 + w - img.lm - img.rm

		// generate the cells
		yl = posy + 3
		y = posy + 3 - font.size * .7
		while (1) {
			cl = cells.shift()
			if (!cl)
				break
			y += hr
			yl += hr
			x = x0 + wc / 2
			while (1) {
				cell = cl.shift()
				if (!cell)
					break
				lc += build_cell(cell, x, y, yl, hr)
				x += wc
			}
		}

		// draw the lines
		line = '<path class="stroke" d="\n'
		y = posy + 3
		for (i = 0; i <= nr; i++) {
			line += 'M' + x0.toFixed(1) + ' ' + y.toFixed(1) +
				'h' + w.toFixed(1)+ '\n'
			y += hr
		}
		x = x0
		y = posy + 3
		for (i = 0; i <= nc; i++) {
			line += 'M' + x.toFixed(1) + ' ' + y.toFixed(1) +
				'v' + (hr * nr).toFixed(1) + '\n'
			x += wc
		}
		line += '"/>\n'

		// insert the cells
		line += lc

		// show the repeat signs
		y = posy + 3 - font.size * .7
		while (1) {
			bl = bars.shift()
			if (!bl)
				break
			x = x0
			y += hr
			while (1) {
				bar = bl.shift()
				if (!bar)
					break
				if (bar[0] == ':')
					line += '<text class="' + cls + '" x="' +
						(x - 5).toFixed(1) +
						'" y="' + y.toFixed(1) +
						'" style="font-weight:bold;font-size:' +
						(font.size * 1.6).toFixed(1) +
						'px">:</text>\n'
				if (bar.slice(-1) == ':')
					line += '<text class="' + cls + '" x="' +
						(x + 5).toFixed(1) +
						'" y="' + y.toFixed(1) +
						'" style="font-weight:bold;font-size:' +
						(font.size * 1.6).toFixed(1) +
						'px">:</text>\n'
				x += wc
			}
		}
		abc.out_svg(line)
		abc.vskip(hr * nr + 6)
	} // build_grid()

	// ----- block_gen ----

	// set the text style
	if (!cfmt.gridfont)
		abc.param_set_font("gridfont", "serif 16")
	font = abc.get_font('grid')
	font_cl = abc.font_class(font)
	cls = font_cl + " mid"
	abc.add_style("\n.mid {text-anchor:middle}")
	abc.set_font('grid')		// (for strwh())

	// scan the grid content
	txt = txt.split('\n')
	while (1) {
		ln = txt.shift()	// line
		if (!ln)
			break

		// extract the bars and the chords
		ln = ln.match(/[|:]+|[^|:\s]+/g)
		bars[nr] = []
		cells[nr] = []
		i = -1
		while (1) {
			cl = ln.shift()
			if (!cl)
				break
			if (cl.match(/[:|]+/)) {
				bars[nr][++i] = cl
				cells[nr][i] = []
			} else {
				if (!cells[nr][i]) {	// if starting '|' is missing
					bars[nr][++i] = '|'
					cells[nr][i] = []
				}
				if (cl == '.' || cl == '-')
					cl = ''
				cells[nr][i].push(cl)
			}
		}
		if (cells[nr][i].length)
			bars[nr][++i] = '|'	// missing ending bar
		else
			cells[nr][i] = null	// keep just the measure bar

		if (i > nc)
			nc = i

		i = 0
		while (1) {
			cl = cells[nr][i++]
			if (!cl)
				break
			if (cl.length == 2) {
				cl[2] = cl[1]	// "| A B |" => "|A - B|"
				cl[1] = ''
			}
			w = abc.strwh(cl.join(''))[0]
			if (w > wc)
				wc = w
		}
		nr++
	}
	wc += abc.strwh('  ')[0]

	// build the grid and insert it in the music
	build_grid()
	abc.blk_flush()
    }, // block_gen()

// handle %%begingrid
    do_begin_end: function(of, type, opt, txt) {
    var	i, s, v,
	lm = -1,		// left margin - default: center the grid
	width			// grid width - default: computed

	if (type != "grid") {
		of(type, opt, txt)
		return
	}

	// replace the accidentals
	txt = txt.replace(/#|=|b/g,
		function(x) {
			switch (x) {
			case '#': return "\u266f"
			case '=': return "\u266e"
//			case 'b': return "\u266d"
			}
			return "\u266d"
		})

	opt = opt.trim().split(/\s+/)
	while (1) {
		i = opt.shift()
		if (!i)
			break
		switch (i[0]) {
		case 'c':			// chord-define
			this.cfmt().csdef = txt
			break
		case 'n':			// noprint
			type = ""
			break
		case 'l':			// lm=..
		case 'w':			// width=..
			v = i.match(/-?[\d.]+.?.?/)
			if (v) {
				v = this.get_unit(v[0])
				if (!isNaN(v)) {
					if (i[0] == 'l')
						lm = v
					else
						width = v
				}
			}
			break
		}
	}

	if (type) {
		type += "3"
		if (this.get_parse().state >= 2) {
			s = this.new_block(type)
			s.text = txt
			s.lm = lm
			s.width = width
		} else {
			abc2svg.grid3.block_gen.call(this, null, {
						subtype: type,
						text: txt,
						lm: lm,
						width: width
						})
		}
	}
    }, // do_begin_end()

    output_music: function(of) {
    var	ln, i, dt, ss, ntim, p_vc, s3, cl,
	C = abc2svg.C,
	abc = this,
	s = abc.get_tsfirst(),
	vt = abc.get_voice_tb(),
	t = abc.cfmt().csdef,
	cs = []

	// add a chord symbol
	function add_cs(ss, ch) {
	    var	s = {			// invisible rest in the voice "grid3"
			type: C.REST,
			fname: ss.fname,
			istart: ss.istart,
			iend: ss.iend,
			v: p_vc.v,
			p_v: p_vc,
			time: ntim,
			st: 0,
			fmt: ss.fmt,
			dur: 0,
			dur_orig: 0,
			invis: true,
			seqst: true,
			nhd: 0,
			notes: [{
				pit: 18,
				dur: 0
			}]
		}

		if (ch != '.' && ch != '-') {
			abc.set_a_gch(s, [{	// define the chord symbol
				type: 'g',
				text: ch,
				otext: ch,
				istart: ss.istart,
				iend: ss.iend,
				font: abc.get_font("gchord"),
				pos: p_vc.pos.gch || C.SL_ABOVE
			}])
		}

		// insert the rest
		if (!p_vc.last_sym) {
			p_vc.sym = s
		} else {
			s.prev = p_vc.last_sym
			s.prev.next = s
		}
		p_vc.last_sym = s
		s.ts_next = ss
		s.ts_prev = ss.ts_prev
		s.ts_prev.ts_next = s
		ss.ts_prev = s
		if (s.time == ss.time)
			delete ss.seqst
		return s
	} // add_cs()

	if (t) {				// if chord-define
		p_vc = {
			id: "grid3",
			v: vt.length,
			time: 0,
			pos: {
				gst: 0
			},
			scale: 1,
			st: 0,
			second: true,
			sls: []
		}
		vt.push(p_vc)	// add the voice supporting the chord symbols

		t = t.split('\n')
		while (1) {			// scan the grid content
			ln = t.shift()		// line
			if (!ln)
				break
			ln = ln.trimLeft()
			if (ln[0] == '|')
				ln = ln.slice(ln[1] == ':' ? 2 : 1)
			if (ln[ln.length - 1] != '|')
				ln = ln + '|'

			// extract the bars and the chords
			ln = ln.match(/[|:]+|[^|:\s]+/g)
			while (1) {
				cl = ln.shift()
				if (!cl)
					break
				if (cl[0] == '|' || cl[0] == ':') {
					while (s && !s.dur)
						s = s.ts_next
					if (!s)
						break
					ss = s			// first note/rest
					while (s && !s.bar_type)
						s = s.ts_next	// end of measure
					if (!cs.length)
						cs = ['.']
					ntim = ss.time
					dt = (s.time - ntim) / cs.length
					s3 = null
					for (i = 0; i < cs.length; i++) {
						if ((cs[i] != '.'
						  && cs[i] != '-')
						 || !s3) {
							while (ss.time < ntim)
								ss = ss.ts_next
							s3 = add_cs(ss, cs[i])
						}
						s3.dur += dt
						s3.dur_orig =
							s3.notes[0].dur = s3.dur
						ntim += dt
					}
					while (s && s.type != C.BAR)
						s = s.ts_next
					ss = {
						type: C.BAR,
						bar_type: "|",
						fname: s.fname,
						istart: s.istart,
						iend: s.iend,
						v: p_vc.v,
						p_v: p_vc,
						st: 0,
						time: s.time,
						dur: 0,
						nhd: 0,
						notes: [{
							pit: 18
						}],
						ts_next: s,
						prev: p_vc.last_sym,
						ts_prev: s.ts_prev
					}
					if (!s)
						break
					ss.fmt = s.fmt
					if (s.seqst) {
						ss.seqst = true
						delete s.seqst
					}
					ss.prev.next =
						ss.ts_prev.ts_next =
							s.ts_prev = ss
					p_vc.last_sym = ss
					cs = []
				} else {
					cs.push(cl)
				}
			}
		}
	}
	of()
    }, // output_music()

    set_hooks: function(abc) {
	abc.block_gen = abc2svg.grid3.block_gen.bind(abc, abc.block_gen)
	abc.do_begin_end = abc2svg.grid3.do_begin_end.bind(abc, abc.do_begin_end)
	abc.output_music = abc2svg.grid3.output_music.bind(abc, abc.output_music)
    }
} // grid3

if (!abc2svg.mhooks)
	abc2svg.mhooks = {}
abc2svg.mhooks.grid3 = abc2svg.grid3.set_hooks