'use strict'

# Returns true if the passed argument <value> is neither null nor undefined
is_valid_value = (value) ->
	((value isnt undefined) and (value isnt null))

# Returns the first value in <values...> that is neither null nor undefined
first_valid_value = (values...) ->
	for value in values
		if is_valid_value(value)
			return value
	return

# Assuming that array <array> can be constructed by repeating another array n times, returns the largest n
get_number_of_repetitions_in_array = (array) ->
	for number_of_repetitions in [array.length..2] by -1
		if ((array.length % number_of_repetitions) is 0)
			repeated_array_length = (array.length // number_of_repetitions)
			for index in [repeated_array_length...array.length] by 1
				if (array[index] isnt array[index % repeated_array_length])
					break
			if (index is array.length)
				return number_of_repetitions
	1

# Returns the greatest common divisor (GCD) of <first_number> and <second_number>
greatest_common_divisor = (first_number, second_number) ->
	return (if first_number then greatest_common_divisor(second_number % first_number, first_number) else second_number)

# Returns the greatest_common_divisor (GCD) of all numbers in array <numbers_array>
greatest_common_divisor_in_array = (numbers_array) ->
	numbers_array.reduce(greatest_common_divisor)

# Returns true if <value> is an array
is_array = (value) ->
	Array.isArray(value)

# Returns a function with <value> argument that returns true if <value> is of type <type_string>
is_of_type = (type_string) ->
	(value) ->
		(typeof(value) is type_string)

# Returns true if <value> is of type "string"
is_object_type = is_of_type('object')

# Returns true if <value> is of type "string"
is_string_type = is_of_type('string')

# The regular expression pattern of a valid logic signal, as a string
logic_signal_pattern = '^\\s*\\[?(\\s*[+-]?\\d+?(?:,\\s*[+-]?\\d+)*)\\]?(?:\\s*@\\s*(\\d+))?(?:\\s*\\*\\s*(\\d+))?\\s*$'

# The regular expression pattern of a valid logic signal, as a RegExp
logic_signal_regexp = (new RegExp(logic_signal_pattern))

# Repeat array <array> <number_of_repetitions> times
repeat_array = (array, number_of_repetitions) ->
	(array[index % array.length] for index in [0...(array.length * number_of_repetitions)] by 1)

# This class represents a logic/binary signal
Logic_Signal = class
	constructor: (data, cycle_time, repetitions) ->
		if is_array(data)
			@cycles = data
			@cycle_time = cycle_time
			@repetitions = repetitions
		else if is_object_type(data)
			@cycles = data.cycles
			@cycle_time = data.cycle_time
			@repetitions = data.repetitions
		else if is_string_type(data)
			data = logic_signal_regexp.exec(data)
			@cycles = (parseInt(cycle_string) for cycle_string in data[1].split(','))
			@cycle_time = data[2]
			@repetitions = data[3]
		else
			throw new Error('Logic signal constructor called with invalid arguments')
		@cycle_time = first_valid_value(@cycle_time, 1)
		@repetitions = first_valid_value(@repetitions, 1)
	get_unrepeated_timings: ->
		@cycles.map((cycle) => (cycle * @cycle_time))
	get_timings: ->
		repeat_array(@get_unrepeated_timings(), @repetitions)
	compress: ->
		timings = @get_timings()
		repetitions = get_number_of_repetitions_in_array(timings)
		unrepeated_timings = timings.slice(0, (timings.length // repetitions))
		cycle_time = greatest_common_divisor_in_array(unrepeated_timings)
		cycles = ((timing // cycle_time) for timing in unrepeated_timings)
		new Logic_Signal(cycles, cycle_time, repetitions)
	to_string: ->
		"#{@cycles.join(',')}@#{@cycle_time}*#{@repetitions}"
	to_timings_string: ->
		@get_timings().join(',')

# What this module exports
module.exports = Logic_Signal
