/*
 * factor(): Return an array of factors of a number
 *
 * Copyright © 2001  Keith Packard.
 * All Rights Reserved.  See the file COPYING in this directory
 * for licensing information.
 *
 * Very inefficient, but demonstrates the use of disjoint unions
 * and dynamic arrays.
 */

namespace Factor {

    public bool is_prime (int i)
    {
	if (i == 1) return false;
	if (i == 2) return true;
	if ((i & 1) == 0) return false;

	int limit = floor (sqrt (i)) + 1;
	int f;

	for (f = 3; f <= limit; f++)
	{
	    if ((i % f) == 0)
		return false;
	}
	return true;
    }

    typedef int_list_struct;
    
    typedef union {
	*int_list_struct    ref;
	void		    end;
    } int_list;
    
    typedef struct {
	int_list	    next;
	int		    v;
    } int_list_struct;

    public int_list primes (int i)
    {
	bool prime_wrt (int_list l, int i)
	{
	    if (l == int_list.end) return true;
	    if (i % l.ref->v == 0) return false;
	    return prime_wrt (l.ref->next, i);
	}

	if (i < 3) 
	    return (int_list.ref) reference ((int_list_struct) { .next = int_list.end, .v = 2 });

	int_list    l = primes (i-1);

	if (prime_wrt (l, i))
	    return (int_list.ref) reference ((int_list_struct) { .next = l, .v = i });
	return l;
    }

    int[*] list_to_array (int_list l)
    {
	int list_length (int_list l)
	{
	    return l != int_list.end ? 1+list_length(l.ref->next) : 0;
	}

	int[*]  a = (int [list_length (l)]) {};
	int	i = 0;
	while (l != int_list.end)
	{
	    a[i] = l.ref->v;
	    l = l.ref->next;
	    i++;
	}
	return a;
    }

    typedef union {
	int[*]  array;
	void	none;
    } array_or_none;

    public array_or_none factor (int i)
    {
	array_or_none array_append (array_or_none a, int v)
	{
	    union switch (a) {
	    case array:
		int[*]  n = (int [dim(a.array)+1]) {};
		int	i;

		for (i = 0; i < dim(a.array); i++)
		    n[i] = a.array[i];
		n[i] = v;
		return (array_or_none.array) n;
	    case none:
		int[*]	n = (int [1]) { v };
		return (array_or_none.array) n;
	    }
	}

	if (i < 0) return array_append (factor (-i), -1);

	array_or_none   result = array_or_none.none;

	int one_factor (int i)
	{
	    if (i == 1) return 1;
	    if ((i & 1) == 0) return 2;

	    int limit = floor (sqrt (i)) + 1;
	    int f;

	    for (f = 3; f <= limit; f += 2)
	    {
		if (!is_prime (f)) continue;
		if ((i % f) == 0)
		    return f;
	    }
	    return i;
	}

	while (i > 1)
	{
	    int	factor = one_factor (i);
	    result = array_append (result, factor);
	    i /= factor;
	}
	return result;
    }
}
