// equalbars.js - module to set equal spaced measure bars
//
// Copyright (C) 2018-2025 Jean-François 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 "%%equalbars" appears in a ABC source.
//
// Parameters
//	%%equalbars bool

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

abc2svg.equalbars = {

    // new tune - clear the width of the start of the staff
    output_music: function(of) {
	this.equalbars_d = 0;
	of()
    },

    // get the equalbars parameter
   set_fmt: function(of, cmd, parm) {
	if (cmd != "equalbars") {
		of(cmd, parm)
		return
	}
    var	fmt = this.cfmt()
	fmt.equalbars = this.get_bool(parm)
	fmt.stretchlast = 1
    },

    // adjust the symbol offsets of a music line
    // only the bars of the first voice are treated
    set_sym_glue: function(of, width) {
    var	C = abc2svg.C,
	s, s2, d, w, i, n, x, g, t, t0, f,
	bars = [],
	tsfirst = this.get_tsfirst();

	of(width)			// compute the x offset of the symbols
	if (!this.cfmt().equalbars)
		return

	// search the first note/rest/space
	for (s2 = tsfirst; s2; s2 = s2.next) {
		switch (s2.type) {
		default:
			continue
		case C.GRACE:
		case C.MREST:
		case C.NOTE:
		case C.REST:
		case C.SPACE:
			break
		}
		break
	}
	if (!s2)
		return

	// build an array of the bars
	t0 = t = s2.time
	for (s = s2; s.next; s = s.next) {
		if (s.type == C.BAR && s.seqst && s.time != t) {
			bars.push([s, s.time - t]);
			t = s.time
		}
	}

	// push the last bar or replace it in the array
	if (s.time != t)
		bars.push([s, s.time - t])
	else
		bars[bars.length - 1][0] = s	// replace the last bar

	t = s.time
	if (s.dur)
		t += s.dur;

	n = bars.length
	if (n <= 1)
		return				// one or no bar

	// if small width, get the widest measure
	if (s.x < width) {
		w = 0
		x = 0
		for (i = 0; i < n; i++) {
			s = bars[i][0]
			if (s.x - x > w)
				w = s.x - x
			x = s.x
		}
		if (w * n < width)
			width = w * n
		this.set_realwidth(width)
	}

	// set the measure parameters
	x = s2.type == C.GRACE ? s2.extra.x : (s2.x - s2.wl)
	if (this.equalbars_d < x)
		this.equalbars_d = x		// new offset of the first note/rest

	d = this.equalbars_d
	w = (width - d) / (t - t0)		// width per time unit

	// loop on the bars
	for (i = 0; i < n; i++) {
		do {			// don't shift the 1st note from the bar
			if (s2.type == C.GRACE) {
				for (g = s2.extra; g; g = g.next)
					g.x = d + g.x - x
			} else {
				s2.x = d + s2.x - x
			}
			s2 = s2.ts_next
		} while (!s2.seqst)

		s = bars[i][0];			// next bar
		f = w * bars[i][1] / (s.x - x)

		// and update the x offsets
		for ( ; s2 != s; s2 = s2.ts_next) {
			if (s2.type == C.GRACE) {
				for (g = s2.extra; g; g = g.next)
					g.x = d + (g.x - x) * f
//			} else if (s2.x) {
			} else {
				s2.x = d + (s2.x - x) * f
			}
		}
		d += w * bars[i][1];
		x = s2.x
		while (1) {
			s2.x = d;
			s2 = s2.ts_next
			if (!s2 || s2.seqst)
				break
		}
		if (!s2)
			break
	}
    }, // set_sym_glue()

    set_hooks: function(abc) {
	abc.output_music = abc2svg.equalbars.output_music.bind(abc, abc.output_music);
	abc.set_format = abc2svg.equalbars.set_fmt.bind(abc, abc.set_format);
	abc.set_sym_glue = abc2svg.equalbars.set_sym_glue.bind(abc, abc.set_sym_glue)
    }
} // equalbars

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