package randomart

/*
 * Draw an ASCII-Art representing the fingerprint so human brain can
 * profit from its built-in pattern recognition ability.
 * This technique is called "random art" and can be found in some
 * scientific publications like this original paper:
 *
 * "Hash Visualization: a New Technique to improve Real-World Security",
 * Perrig A. and Song D., 1999, International Workshop on Cryptographic
 * Techniques and E-Commerce (CrypTEC '99)
 * sparrow.ece.cmu.edu/~adrian/projects/validation/validation.pdf
 *
 * The subject came up in a talk by Dan Kaminsky, too.
 *
 * If you see the picture is different, the key is different.
 * If the picture looks the same, you still know nothing.
 *
 * The algorithm used here is a worm crawling over a discrete plane,
 * leaving a trace (augmenting the field) everywhere it goes.
 * Movement is taken from dgst_raw 2bit-wise.  Bumping into walls
 * makes the respective movement vector be ignored for this turn.
 * Graphs are not unambiguous, because circles in graphs can be
 * walked in either direction.
 */

import (
	"os"
)

func MAX(a, b int) int {
	if a > b {
		return a
	}
	return b
}

func MIN(a, b int) int {
	if a < b {
		return a
	}
	return b
}

/*
 * Field sizes for the random art.  Have to be odd, so the starting point
 * can be in the exact middle of the picture, and FLDBASE should be >=8 .
 * Else pictures would be too dense, and drawing the frame would
 * fail, too, because the key type would not fit in anymore.
 */
const (
	FLDBASE   = 8
	FLDSIZE_Y = (FLDBASE + 1)
	FLDSIZE_X = (FLDBASE*2 + 1)
)

func FromString(str string) string {
	ch := make(chan byte)

	go func() {
		defer close(ch)
		for _, v := range []byte(str) {
			ch <- v
		}
	}()
	return key_fingerprint_randomart(ch)
}

func FromFile(file *os.File) string {
	ch := make(chan byte)

	go func() {
		defer close(ch)
		// TODO make input a 1 element byte array
		input := make([]byte, 1)
		nread, err := file.Read(input)
		for err == nil && nread > 0 {
			ch <- input[0]
			nread, err = file.Read(input)
		}
	}()
	return key_fingerprint_randomart(ch)
}

func key_fingerprint_randomart(ch chan byte) string {
	/*
	 * Chars to be used after each other every time the worm
	 * intersects with itself.  Matter of taste.
	 */
	augment_string := " .o+=*BOX@%&#/^SE"
	var field [FLDSIZE_X][FLDSIZE_Y]byte
	len_aug := len(augment_string) - 1
	var retval [(FLDSIZE_X + 3) * (FLDSIZE_Y + 2)]byte

	/* initialize field */
	x := FLDSIZE_X / 2
	y := FLDSIZE_Y / 2

	/* process raw key */
	for input, ok := <-ch; ok; input, ok = <-ch {
		/* each byte conveys four 2-bit move commands */
		for b := 0; b < 4; b++ {
			/* evaluate 2 bit, rest is shifted later */
			if input&0x1 > 0 {
				x += 1
			} else {
				x += -1
			}

			if input&0x2 > 0 {
				y++
			} else {
				y--
			}

			/* assure we are still in bounds */
			x = MAX(x, 0)
			y = MAX(y, 0)
			x = MIN(x, FLDSIZE_X-1)
			y = MIN(y, FLDSIZE_Y-1)

			/* augment the field */
			if int(field[x][y]) < len_aug-2 {
				field[x][y]++
			}
			input = input >> 2
		}
	}

	/* mark starting point and end point*/
	field[FLDSIZE_X/2][FLDSIZE_Y/2] = byte(len_aug - 1)
	field[x][y] = byte(len_aug)

	i := 0
	retval[i] = '+'
	i++

	/* output upper border */
	for x := 0; x < FLDSIZE_X; x++ {
		retval[i] = '-'
		i++
	}
	retval[i] = '+'
	i++
	retval[i] = '\n'
	i++

	/* output content */
	for y := 0; y < FLDSIZE_Y; y++ {
		retval[i] = '|'
		i++
		for x := 0; x < FLDSIZE_X; x++ {
			retval[i] = augment_string[MIN(int(field[x][y]), len_aug)]
			i++
		}
		retval[i] = '|'
		i++
		retval[i] = '\n'
		i++
	}

	/* output lower border */
	retval[i] = '+'
	i++
	for j := 0; j < FLDSIZE_X; j++ {
		retval[i] = '-'
		i++
	}
	retval[i] = '+'
	i++

	return string(retval[0:i])
}
