// abc2svg - grid.js - module to insert a chord grid before or after a tune
//
// Copyright (C) 2018-2023 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 "%%grid" appears in a ABC source.
//
// Parameters
//	%%grid <n> [include=<list>] [nomusic] [norepeat] [repbrk] [parts]
//		<n> = number of columns (1: auto)
//			> 0: above the tune, < 0: under the tune
//		<list> = comma separated list of (continuous) measure numbers
//		'nomusic' displays only the grid
//		'norepeat' omits the ':' indications
//		'repbrk' starts a new grid line on start/stop repeat
//		'parts' displays the parts on the left side of the grid
//	%%gridfont font_name size (default: 'serif 16')

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

abc2svg.grid = {
    pl: '<path class="stroke" stroke-width="1" d="M',

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

    var	abc = this,
	img, cls,
	cfmt = abc.cfmt(),
	grid = cfmt.grid

// generate the grid
function build_grid(s, font) {
    var	i, k, l, nr, bar, w, hr, x0, x, y, yl, ps, d,
	lc = '',
	chords = s.chords,
	bars = s.bars,
	parts = s.parts || [],
	wmx = s.wmx,
	cells = [],
	nc = grid.n

	// set some chord(s) in each cell
	function set_chords() {
	    var	i, ch,
		pch = '-'

		for (i = 0; i < chords.length; i++) {
			ch = chords[i]
			if (!ch[0])
				ch[0] = pch
			if (ch.length == 0)
				continue
			if (ch.length == 1) {
				pch = ch[0]
				continue
			}
			if (ch.length == 2) {
				ch[2] = ch[1];
				ch[1] = null;
				pch = ch[2]
				continue
			}
			if (ch.length == 3) {
				pch = ch[2]
				continue
			}
			if (!ch[2])
				ch[2] = ch[1] || ch[0];
			pch = ch[3]
		}
	} // set_chords()

	function build_cell(cell, x, y, yl, hr) {
		if (cell.length > 1) {
			abc.out_svg(abc2svg.grid.pl)		// / line
			abc.out_sxsy(x - wmx / 2, ' ', yl)
			abc.out_svg('l' +
				wmx.toFixed(1) + ' -' + hr.toFixed(1) +
				'"/>\n')
			if (cell[1]) {
			    abc.out_svg(abc2svg.grid.pl)	// \ left line
			    abc.out_sxsy(x - wmx / 2, ' ', yl + hr)
			    abc.out_svg('l' +
				(wmx / 2).toFixed(1) + ' ' + (hr / 2).toFixed(1) +
				'"/>\n')
			    abc.set_font('gs')			// small font
			    abc.xy_str(x - wmx / 3, y, cell[0])
			    abc.xy_str(x, y + hr / 3, cell[1])
			} else {
			    abc.set_font('gs')
			    abc.xy_str(x - wmx * .2, y + hr / 4, cell[0])
			}
			if (cell.length >= 3) {
			  if (cell[3]) {
			    abc.out_svg(abc2svg.grid.pl)	// \ right line
			    abc.out_sxsy(x, ' ', yl + hr / 2)
			    abc.out_svg('l' +
				(wmx / 2).toFixed(1) + ' ' + (hr / 2).toFixed(1) +
				'"/>\n')
			    abc.set_font('gs')
			    abc.xy_str(x, y - hr / 3, cell[2])
			    abc.xy_str(x + wmx / 3, y, cell[3])
			  } else {
			    abc.set_font('gs')
			    abc.xy_str(x + wmx * .2, y - hr / 4, cell[2])
			  }
			}
		} else {
			abc.set_font('grid')
			abc.xy_str(x, y, cell[0])
		}
	} // build_cell()

	// draw the horizontal lines
	function draw_hl() {
	    var	i, i1, j, x,
		y = -1

		for (i = 0; i <= nr + 1; i++) {
			j = 0
			i1 = i > 0 ? i - 1 : 0
			while (1) {
				while (j <= nc && !d[i1][j])
					j++
				if (j > nc)
					break
				x = wmx * j
				while (j <= nc && d[i1][j])
					j++
				if (i && i1 < nr) {
					while (j <= nc && d[i1 + 1][j])
						j++
				}
				abc.out_svg('M')
				abc.out_sxsy(x0 + x, ' ', y)
				abc.out_svg('h' + (wmx * j - x).toFixed(1)+ '\n')
			}
			y -= hr
		}
	} // draw_hl()

	// draw the vertical lines
	function draw_vl() {
	    var	i, i1, j, y,
		x = x0

		for (i = 0; i <= nc; i++) {
			j = 0
			i1 = i > 0 ? i - 1 : 0
			while (1) {
				while (j <= nr && !d[j][i1])
					j++
				if (j > nr)
					break
				y = hr * j
				while (j <= nr && d[j][i1])
					j++
				abc.out_svg('M')
				abc.out_sxsy(x, ' ', -y - .5)
				abc.out_svg('v' + (hr * j - y + 1).toFixed(1) + '\n')
			}
			x += wmx
		}
	} // draw_vl()

	// ------- build_grid() -------

	// set some chords in each cell
	set_chords()

	// build the content of the cells
	if (!grid.ls) {
		cells = chords
	} else {				// with list of mesure numbers
		bar = bars;
		bars = [ ]
		ps = parts
		parts = []
		for (i = 0; i < grid.ls.length; i++) {
			l = grid.ls[i]
			if (l.indexOf('-') < 0)
				l = [l, l]
			else
				l = l.split('-')
			for (k = l[0] - 1; k < l[1]; k++) {
				if (!chords[k])		// error
					break
				cells.push(chords[k]);
				bars.push(bar[k])
				parts.push(ps[k])
			}
		}
		bars.push(bar[k])		// ending bar
	}

	// get the number of columns
	if (nc < 0)
		nc = -nc
	if (nc < 3)				// auto
		nc = cells.length % 6 == 0 ? 6 : 8
	if (nc > cells.length)
		nc = cells.length;

	hr = font.size * 2
	if (wmx < hr * 1.5)
		wmx = hr * 1.5				// cell width

	x0 = img.width - img.lm - img.rm		// staff width
	w = wmx * nc
	if (w > x0) {
		nc /= 2;
		w /= 2
	}

	// generate the cells
	yl = -1
	y = -1 + font.size * .6
	nr = -1
	x0 = (x0 / cfmt.scale - w) / 2
	d = []
	for (i = 0; i < cells.length; i++) {
		if (i == 0
		 || (grid.repbrk
		  && (bars[i].slice(-1) == ':' || bars[i][0] == ':'))
		 || parts[i]
		 || k >= nc) {
			y -= hr			// new row
			yl -= hr
			x = x0 + wmx / 2
			k = 0
			nr++
			d[nr] = []
		}
		d[nr][k] = 1
		k++
		build_cell(cells[i], x, y, yl, hr)
		x += wmx
	}

	// draw the lines
	abc.out_svg('<path class="stroke" stroke-width="1" d="\n')
	draw_hl()
	draw_vl()
	abc.out_svg('"/>\n')

	// show the repeat signs and the parts
	y = -1 + font.size * .7
	x = x0
	for (i = 0; i < bars.length; i++) {
		bar = bars[i]
		if (bar[0] == ':') {
			abc.out_svg('<text class="' + cls + '" x="')
			abc.out_sxsy(x - 5, '" y="', y)
			abc.out_svg('" style="font-weight:bold;font-size:' +
				(font.size * 1.5).toFixed(1) + 'px">:</text>\n')
		}
		if (i == 0
		 || (grid.repbrk
		  && (bar.slice(-1) == ':' || bar[0] == ':'))
		 || parts[i]
		 || k >= nc) {
			y -= hr;			// new row
			x = x0
			k = 0
			if (parts[i]) {
				w = abc.strwh(parts[i])[0]
				abc.out_svg('<text class="' + cls + '" x="')
				abc.out_sxsy(x - 2 - w, '" y="', y)
				abc.out_svg('" style="font-weight:bold">' +
					parts[i] + '</text>\n')
			}
		}
		k++
		if (bar.slice(-1) == ':') {
			abc.out_svg('<text class="' + cls + '" x="')
			abc.out_sxsy(x + 5, '" y="', y)
			abc.out_svg('" style="font-weight:bold;font-size:' +
				(font.size * 1.5).toFixed(1) + 'px">:</text>\n')
		}
		x += wmx
	}
	abc.vskip(hr * (nr + 1) + 6)
} // build_grid()

	// ----- block_gen() -----
    var	p_voice, n, font, f2

	abc.set_page()
	img = abc.get_img()

	// set the text style
	font = abc.get_font('grid')
	if (font.class)
		font.class += ' mid'
	else
		font.class = 'mid'
	cls = abc.font_class(font)

	// define a smaller font
	abc.param_set_font("gsfont",
		font.name + ' ' + (font.size * .7).toFixed(1))
	f2 = cfmt.gsfont
	if (font.weight)
		f2.weight = font.weight
	if (font.style)
		f2.style = font.style
	f2.class = font.class
	abc.add_style("\n.mid {text-anchor:middle}")

	// create the grid
	abc.blk_flush()
	build_grid(s, font)
	abc.blk_flush()
    }, // block_gen()

    set_stems: function(of) {
    var	C = abc2svg.C,
	abc = this,
	tsfirst = abc.get_tsfirst(),
	voice_tb = abc.get_voice_tb(),
	cfmt = abc.cfmt(),
	grid = cfmt.grid

	// extract one of the chord symbols
	// With chords as "[yyy];xxx"
	// (!sel - default) returns "yyy" and (sel) returns "xxx"
	function cs_filter(a_cs) {
	    var	i, cs, t

		for (i = 0; i < a_cs.length; i++) {
			cs = a_cs[i]
			if (cs.type != 'g')
				continue
			t = cs.text
			if (cfmt.altchord) {
				for (i++; i < a_cs.length; i++) {
					cs = a_cs[i]
					if (cs.type != 'g')
						continue
					t = cs.text
					break
				}
			}
			return t.replace(/\[|\]/g, '')
		}
	} // cs_filter()

	function get_beat(s) {
	    var	beat = C.BLEN / 4

		if (!s.a_meter[0] || s.a_meter[0].top[0] == 'C'
		 || !s.a_meter[0].bot)
			return beat
		beat = C.BLEN / s.a_meter[0].bot[0] |0
		if (s.a_meter[0].bot[0] == 8
		 && s.a_meter[0].top[0] % 3 == 0)
			beat = C.BLEN / 8 * 3
		return beat
	} // get_beat()

	// build the arrays of chords and bars
	function build_chords(sb) {		// block 'grid'
	    var	s, i, w, bt, rep,
		bars = [],
		chords = [],
		parts = [],
		chord = [],
		beat = get_beat(voice_tb[0].meter),
		wm = voice_tb[0].meter.wmeasure,
		cur_beat = 0,
		beat_i = 0,
		wmx = 0,
		some_chord = 0

		// scan the music symbols
		bars.push('|')
		for (s = tsfirst; s; s = s.ts_next) {
			while (s.time > cur_beat) {
				if (beat_i < 3)	// only 2, 3 or 4 beats / measure...
					beat_i++
				cur_beat += beat
			}
			if (s.part)
				parts[chords.length] = s.part.text
			switch (s.type) {
			case C.NOTE:
			case C.REST:
			case C.SPACE:
				if (!s.a_gch || chord[beat_i])
					break
				bt = cs_filter(s.a_gch)
				if (!bt)
					break
					w = abc.strwh(bt.replace(
						/<[^>]+>/gm,''))
					if (w[0] > wmx)
						wmx = w[0]
					bt = new String(bt)
					bt.wh = w
				chord[beat_i] = bt
				break
			case C.BAR:
				i = s.bar_num		// check if normal measure bar
				bt = s.bar_type
				while (s.ts_next && s.ts_next.time == s.time) {
					if (s.ts_next.dur
					 || s.ts_next.type == C.SPACE)
						break
					s = s.ts_next
					if (s.type == C.METER) {
						beat = get_beat(s)
						wm = s.wmeasure
						continue
					}
					if (s.type != C.BAR)
						continue
					if (s.bar_type[0] == ':'
					 && bt[0] != ':')
						bt = ':' + bt
					if (s.bar_type.slice(-1) == ':'
					 && bt.slice(-1) != ':')
						bt += ':'
					if (s.bar_num)
						i = s.bar_num
					if (s.part)
						parts[chords.length + 1] = s.part.text
				}
				if (grid.norep)
					bt = '|'
				if (s.time < wm) {		// if anacrusis
					if (chord.length) {
						chords.push(chord)
						bars.push(bt)
					} else {
						bars[0] = bt
					}
				} else {
					if (!i)		// if not normal measure bar
						break
					chords.push(chord)
					bars.push(bt)
				}
				if (chord.length)
					some_chord++
				chord = []
				cur_beat = s.time	// synchronize in case of error
				beat_i = 0
				if (bt.indexOf(':') >= 0)
					rep = true	// some repeat
				break
			case C.METER:
				beat = get_beat(s)
				wm = s.wmeasure
				break
			}
		}

		if (chord.length) {
			bars.push('')
			chords.push(chord)
		}
		if (!some_chord)
			return			// no chord in this tune

		wmx += abc.strwh(rep ? '    ' : '  ')[0]

		sb.chords = chords
		sb.bars = bars
		if (grid.parts && parts.length)
			sb.parts = parts
		sb.wmx = wmx
	} // build_chords

	// -------- set_stems --------

	// create a specific block
	if (grid) {
	    var	C = abc2svg.C,
		tsfirst = this.get_tsfirst(),
		fmt = tsfirst.fmt,
		voice_tb = this.get_voice_tb(),
		p_v = voice_tb[this.get_top_v()],
		s = {
			type: C.BLOCK,
			subtype: 'grid',
			dur: 0,
			time: 0,
			p_v: p_v,
			v: p_v.v,
			st: p_v.st
		}

		if (!cfmt.gridfont)
			abc.param_set_font("gridfont", "serif 16")
		abc.set_font('grid')
		build_chords(s)			// build the array of the chords

		// and insert it in the tune
		if (!s.chords) {		// if no chord
			;
		} else if (grid.nomusic) {	// if just the grid
			this.set_tsfirst(s)
		} else if (grid.n < 0) {	// below
			for (var s2 = tsfirst; s2.ts_next; s2 = s2.ts_next)
				;
			s.time = s2.time
			s.prev = p_v.last_sym.prev // before the last symbol
			s.prev.next = s
			s.next = p_v.last_sym
			p_v.last_sym.prev = s

			s.ts_prev = s2.ts_prev
			s.ts_prev.ts_next = s
			s.ts_next = s2
			s2.ts_prev = s
			if (s2.seqst) {
				s.seqst = true
				s2.seqst = false
			}
		} else {			// above
			s.next = p_v.sym
			s.ts_next = tsfirst
			tsfirst.ts_prev = s
			this.set_tsfirst(s)
			p_v.sym.prev = s
			p_v.sym = s
		}
		s.fmt = s.prev ? s.prev.fmt : fmt
	}
	of()
    }, // set_stems()

    set_fmt: function(of, cmd, parm) {
	if (cmd == "grid") {
		if (!parm)
			parm = "1";
		parm = parm.split(/\s+/)
		var grid = {n: Number(parm.shift())}
		if (isNaN(grid.n)) {
			if (parm.length) {
				this.syntax(1, this.errs.bad_val, "%%grid")
				return
			}
			grid.n = 1
		}
		while (parm.length) {
			var item = parm.shift()
			if (item == "norepeat")
				grid.norep = true
			else if (item == "nomusic")
				grid.nomusic = true
			else if (item == "parts")
				grid.parts = true
			else if (item == "repbrk")
				grid.repbrk = true
			else if (item.slice(0, 8) == "include=")
				grid.ls = item.slice(8).split(',')
		}
		this.cfmt().grid = grid
		return
	}
	of(cmd, parm)
    },

    set_hooks: function(abc) {
	abc.block_gen = abc2svg.grid.block_gen.bind(abc, abc.block_gen)
	abc.set_stems = abc2svg.grid.set_stems.bind(abc, abc.set_stems)
	abc.set_format = abc2svg.grid.set_fmt.bind(abc, abc.set_format)
    }
} // grid

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