/*
 *  five_bit_encoding.h
 *  Coda
 *
 *  Created by Ian Cely on 4/10/07.
 *  Copyright 2007 Panic Inc. All rights reserved.
 *
 */

#ifndef __FIVE_BIT_ENCODING
#define __FIVE_BIT_ENCODING 1

#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>

static char five_bit_table[] = { 
    'A', 'B', 'C', 'D', 'E', 'F', 'G', 
    'H', 'J', 'K', 'L', 'M', 'N', 'P', 
    'Q', 'R', 'S', 'T', 'U', 'V', 'W', 
    'X', 'Y', 'Z', '2', '3', '4', '5', 
    '6', '7', '8', '9'
};


// output must be at lease ceil((8.0 / 5.0) * input_len)... returns actual output length

static __inline__ __attribute__((always_inline)) NSUInteger five_bit_encode(const char* input, char* output, NSUInteger input_len)
{
    const char* buf;
    char* outbuf;
	
    NSUInteger cur_in = 0;
    NSUInteger cur_out = 0;
	
    while ( cur_in < input_len)
    {
        NSUInteger remaining = (input_len - cur_in);
        NSUInteger readlen = (remaining > 5) ? 5 : remaining;
        NSUInteger outlen = 0;
        
        outbuf = (output + cur_out);
        buf = (input + cur_in);
		
        switch ( readlen )
        {
			case 5: 
				outbuf[7] = five_bit_table[(buf[4] & 0x1f)];
				outbuf[6] = five_bit_table[((((buf[3] << 3) & 0x18) | ((buf[4] >> 5) & 0x7)) & 0x1f)];
				outlen += 2;
			case 4: 
				outbuf[5] = five_bit_table[((buf[3] >> 2) & 0x1f)];
				outbuf[4] = five_bit_table[((((buf[2] << 1) & 0x1e) | ((buf[3] >> 7) & 0x1)) & 0x1f)];
				outlen += 2;
			case 3:
				outbuf[3] = five_bit_table[((((buf[1] << 4) & 0x10) | ((buf[2] >> 4) & 0xf)) & 0x1f)];
				outlen += 1;
			case 2:
				outbuf[2] = five_bit_table[((buf[1] >> 1) & 0x1f)];
				outbuf[1] = five_bit_table[((((buf[0] << 2) & 0x1c) | ((buf[1] >> 6) & 0x3)) & 0x1f)];
				outlen += 2;
			case 1:
				outbuf[0] = five_bit_table[((buf[0] >> 3) & 0x1f)];
				outlen += 1;
			default:
				break;
        }
		
        // clean up remaining bits
		
        switch ( readlen )
        {
			case 4:
				outbuf[6] = five_bit_table[((buf[3] << 3) & 0x18)];
				++outlen;
				break;
				
			case 3:
				outbuf[4] = five_bit_table[((buf[2] << 1) & 0x1e)];
				++outlen;
				break;
				
			case 2:
				outbuf[3] = five_bit_table[((buf[1] << 4) & 0x10)];
				++outlen;
				break;
				
			case 1:
				outbuf[1] = five_bit_table[((buf[0] << 2) & 0x1c)];
				++outlen;
				break;
				
			default:
				break;
        }
		
        cur_out += outlen;
        cur_in += readlen;
    }
    
    return cur_out;
}


static __inline__ __attribute__((always_inline)) int five_bit_char_to_idx(char in_char)
{
    int ret_val = 0;
	
    switch ( in_char )
    {
		case 'A':
			ret_val = 0;
			break;
		case 'B':
			ret_val = 1;
			break;
		case 'C':
			ret_val = 2;
			break;
		case 'D':
			ret_val = 3;
			break;
		case 'E':
			ret_val = 4;
			break;
		case 'F':
			ret_val = 5;
			break;
		case 'G':
			ret_val = 6;
			break;
		case 'H':
			ret_val = 7;
			break;
		case 'J':
			ret_val = 8;
			break;
		case 'K':
			ret_val = 9;
			break;
		case 'L':
			ret_val = 10;
			break;
		case 'M':
			ret_val = 11;
			break;
		case 'N':
			ret_val = 12;
			break;
		case 'P':
			ret_val = 13;
			break;
		case 'Q':
			ret_val = 14;
			break;
		case 'R':
			ret_val = 15;
			break;
		case 'S':
			ret_val = 16;
			break;
		case 'T':
			ret_val = 17;
			break;
		case 'U':
			ret_val = 18;
			break;
		case 'V':
			ret_val = 19;
			break;
		case 'W':
			ret_val = 20;
			break;
		case 'X':
			ret_val = 21;
			break;
		case 'Y':
			ret_val = 22;
			break;
		case 'Z':
			ret_val = 23;
			break;
		case '2':
			ret_val = 24;
			break;
		case '3':
			ret_val = 25;
			break;
		case '4':
			ret_val = 26;
			break;
		case '5':
			ret_val = 27;
			break;
		case '6':
			ret_val = 28;
			break;
		case '7':
			ret_val = 29;
			break;
		case '8':
			ret_val = 30;
			break;
		case '9':
			ret_val = 31;
			break;
    }
	
    return ret_val;
}

// output must be at lease ceil((5.0 / 8.0) * input_len)... returns actual output length

static __inline__ __attribute__((always_inline)) NSUInteger five_bit_decode(const char* input, char* output, NSUInteger input_len)
{
    const char* buf;
    char* outbuf;
	
    NSUInteger cur_in = 0;
    NSUInteger cur_out = 0;
	
    while ( cur_in < input_len)
    {
        NSUInteger remaining = (input_len - cur_in);
        NSUInteger readlen = (remaining > 8) ? 8 : remaining;
        NSUInteger outlen = 0;
        
        outbuf = (output + cur_out);
        buf = (input + cur_in);
		
        switch ( readlen )
        {
			case 8:
				outbuf[4] = ((five_bit_char_to_idx(buf[7]) & 0x1f) | ((five_bit_char_to_idx(buf[6]) << 5)  & 0xe0));
				++outlen;
			case 7:
				outbuf[3] = (((five_bit_char_to_idx(buf[6]) >> 3) & 0x3) | ((five_bit_char_to_idx(buf[5]) << 2) & 0x7c) | ((five_bit_char_to_idx(buf[4]) << 7) & 0x80));
				++outlen;
			case 6:            
			case 5: 
				outbuf[2] = (((five_bit_char_to_idx(buf[4]) >> 1) & 0xf) | ((five_bit_char_to_idx(buf[3]) << 4) & 0xf0));
				++outlen;
			case 4: 
				outbuf[1] = (((five_bit_char_to_idx(buf[1]) << 6) & 0xc0) | ((five_bit_char_to_idx(buf[2]) << 1) & 0x3e) | ((five_bit_char_to_idx(buf[3]) >> 4) & 0x1));
				++outlen;
			case 3:
			case 2:
				outbuf[0] = (((five_bit_char_to_idx(buf[1]) >> 2) & 0x7) | ((five_bit_char_to_idx(buf[0]) << 3) & 0xf8));
				++outlen;
			case 1:
			default:
				break;
        }
		
        // don't need to clean up remaining bits because they are not significant
		
        cur_out += outlen;
        cur_in += readlen;
    }
    
    return cur_out;
}


static __inline__ __attribute__((always_inline)) int is_valid_five_bit_char(char in_char)
{
	int retVal = 0;
	int idx = five_bit_char_to_idx(in_char);
	
	// Only approve idx 0 if the in_char is 'A' because five_bit_char_to_idx() 
	// has to return 0 by default
	
	if ( idx == 0 )
		retVal = (in_char == 'A'); 
	else
		retVal = 1;
	
	return retVal;
}


#endif