/*
 * Ezpwd Reed-Solomon -- Reed-Solomon encoder / decoder library
 * 
 * Copyright (c) 2014, Hard Consulting Corporation.
 *
 * Ezpwd Reed-Solomon is free software: you can redistribute it and/or modify it under the terms of
 * the GNU General Public License as published by the Free Software Foundation, either version 3 of
 * the License, or (at your option) any later version.  See the LICENSE file at the top of the
 * source tree.  Ezpwd Reed-Solomon is also available under Commercial license.  c++/ezpwd/rs_base
 * is redistributed under the terms of the LGPL, regardless of the overall licensing terms.
 * 
 * Ezpwd Reed-Solomon 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 General Public License for more details.
 */
#ifndef _EZPWD_EZCOD
#define _EZPWD_EZCOD

#include <math.h> // M_PI
#include <cmath>
#include <cctype>

#include <cstdint>
#include <ezpwd/rs>
#include <ezpwd/output>
#include <ezpwd/serialize>

// 
// EZCOD 3:10 location code w/ Reed-Solomon Error Correction, and average 3m accuracy
// 
// - each successive symbol provides greater precision
//   - codes nearby each-other are identical in leading characters
// - average 3m precision achieved in 9 symbols
//   - more than 4 base-10 digits of precision in both lat and lon after the decimal
// - from 1 to 3 symbols of Reed-Solomon parity
//   - 1 parity symbol supplies validation w/ strength equivalent to a check character
//   - 2 parity symbols provides correction of 1 lost symbol (no errors)
//   - 3 parity symbols provides correction of any 1 error, with verification,
//     or recovery of up to any 3 lost symbols (with no other errors)
// 

// 
//     To achieve at least 4 decimal digits of precision after the decimal point, we must have
// defined lat to within 1 part in 1,800,000, and lon to within 1 part in 3,600,000.  As each symbol
// supplies bits, we'll refine the computed lat/lon further, reducing the outstanding fraction of
// "parts" yet to be defined.
// 
//             bits      
//    symbols   latitude             longitude
//              bits mul   parts      bits mul   parts
//       1      2    4         4      3    8         8
//       2      2    4        16      3    8        64
//       3      3    8       128      2    4       256 // not quite integer lat/lon accuracy
// 
//       4      2    4       512      3    8     2,048
//       5      3    8     4,096      2    4     8,192
//       6      2    4    16,384      3    8    65,536 
//
//       7      3    8   131,072      2    4   262,144
//       8      2    4   524,288      3    8 2,097,152
//       9      3    8 4,194,304      2    4 8,388,608 parts resolution in  3:10 code
//                over [-90, 90]       over [-180,180] yields ~3m resolution
// 
//                 vs. 1,800,000             3,600,000 parts resolution in 10:10 code
//                over [-90, 90]       over [-180,180] yields ~10m resolution
// 
//     Therefore, within 9 symbols we define lat and lon with better than double the precision of
// 10:10 code's 4 decimal digits after the decimal point.  This yields an approximate lineal
// precision of 40,075,000m / 8,388,608 == ~5m in both dimensions at the equator, vs. 40,075,000m /
// 3,600,000 == ~11m for 10:10 codes.
// 
//     The 10:10 code provides a single check character, which provides about P(1-1/32) certainty
// that the provided code is correct.  With EZCOD 3:10/11/12 codes, we provide varying levels of 
// detection/correction strength.
// 
//   - 1 parity symbol: act as a check character (like 10:10 codes), or provide 1 symbol of erasure
//     (lost symbol) recovery with no excess parity for validation.
// 
//    - 2 parity symbols: provide 1 symbol of erasure correction (w/ no other errors) with 1 excess parity
//      symbol for validation, or 1 symbol of error detection with no excess parity for validation.
//             
//    - 3 parity symbols: correct 1 error anywhere w/ 1 excess parity symbol for validation, or up
//      to 3 erasures with no excess parity for validation.
// 
//     Therefore, we'll provide Reed-Solomon RS(31,28-30) error correction (5 bit symbols,
// indicating 31 symbols in the field, and from 1 to 3 roots, therefore up to 28 data symbols in the
// field) over the 9 lat/lon data symbols.
// 
// 
// MINIMIZING ERROR
// 
//     Each input lat/lon coordinate will be effectively truncated by the encoding procedure to the
// level of precision (parts) encoded by each symbol.  Subsequent symbols then add their (smaller)
// parts to increase precision.
// 
//     After the last symbol, we know that the actual input coordinates where somewhere
// within the rectangle:
// 
//         [0,0] -> [0,lon_precision] -> [lat_precision,lon_precision] -> [lat_precision,0]
// 
//     At first glance, the best way is to perform rounding instead of truncation on ecoding, by
// simply adding 1/2 of the precision.  Then, the unmodified output lat/lon decoded represents the
// point nearest actual input coordinate.  However, this is NOT ideal.  Remember -- the decoding may
// not have access to all the symbols!  We want to minimize the error even if only some of the
// symbols are available.  Thus, we must apply a correction on decoding.
// 
//    One way gain rounding instead of truncation on decoding is, after adding the last symbol's
// precision, to add 50% of the value represented by the first bit of the next (missing) symbol's
// precision parts.  This would be analogous to receiving the first 2 digits of a 3 digit number:
// 
//         original:  123
//         received:  12_
//         range:     [120,130)
//         guessed:   125 (add 1/2 of the parts represented by the missing digit)
// 
//     If this is done, then the resulting coordinate would be in the middle of the rectangle of
// possible input lat/lon values that could have resulted in the encoded value.  This also works if
// we don't receive and decode all of the symbols; We'll end up with a lat/lon in the middle of the
// (large) rectangle of possible input coordinates.
// 


namespace ezpwd {

    class ezcod_base {
    public:
	double			latitude;		// [-90,+90] angle, degrees
	double			latitude_error;		// total error bar, in meters
	double			longitude;		// [-180,180]
	double			longitude_error;	// total error bar, in meters
	double			accuracy;		// linear accuracy radius, in meters
	int			confidence;		// % parity in excess of last decode
	double			certainty;		//   and the associated probability

	explicit		ezcod_base(
				    double	_lat	= 0,
				    double	_lon	= 0 )
				    : latitude( _lat )
				    , latitude_error( 0 )
				    , longitude( _lon )
				    , longitude_error( 0 )
				    , accuracy( 0 )
				    , confidence( 100 )
				    , certainty( 1 )
	{
	    ;
	}
	virtual		       ~ezcod_base()
	{
	    ;
	}

	typedef std::pair<unsigned char, unsigned char>
				symbols_t;
	virtual symbols_t	symbols()
	    const
	= 0;
	virtual std::ostream   &output(
				    std::ostream       &lhs )
	    const
	= 0;
	virtual std::string	encode(
				    unsigned		_preci	= 0 )	// override precision
	    const
	= 0;
	virtual int		decode(
				    const std::string  &_ezcod )
	= 0;
    };
} // namespace ezpwd

inline std::ostream	       &operator<<(
				    std::ostream	&lhs,
				    const ezpwd::ezcod_base
				    			&rhs )
{
    return rhs.output( lhs );
}

namespace ezpwd {

    // 
    // ezcod<P,L> -- defaults to 1 PARITY and 9 location symbols (3m) of PRECISION
    // 
    template < unsigned P=1, unsigned L=9 >
    class ezcod
	: public ezcod_base {

    private:
	typedef std::array<symbols_t, 12>
				bits_t;
	static const bits_t	bits;
	typedef std::array<std::pair<uint32_t, uint32_t>, 12>
				parts_t;
	static const parts_t	parts;

#if defined( DEBUG )
    public:
#endif
	static const ezpwd::RS<31,31-P>
				rscodec;

    public:
	static constexpr const unsigned	PARITY		= P;	// specified symbols of R-S parity
	static constexpr const unsigned	PRECISION	= L;	// default symbols of location precision
	static constexpr const unsigned	CHUNK		= 3;	// default chunk size

	static constexpr const char	SEP_NONE	= -1;
	static constexpr const char	SEP_DEFAULT	= 0;
	static constexpr const char	SEP_DOT		= '.';
	static constexpr const char	SEP_BANG	= '!';
	static constexpr const char	SEP_SPACE	= ' ';

	static constexpr const char	CHK_NONE	= -1;
	static constexpr const char	CHK_DEFAULT	= 0;
	static constexpr const char	CHK_DASH	= '-';
	static constexpr const char	CHK_SPACE	= ' ';

	unsigned		precision;
	unsigned		chunk;			// Location symbol chunk sizes
	char			separator;		// Separator between location and parity symbols
	char			space;			// Fill space between location symbol chunks

	// 
	// ezcod<P,L>() -- supply non-defaults for location precision, chunk size, etc.
	// 
	explicit		ezcod(
				    double		_lat	= 0,
				    double		_lon	= 0,
				    unsigned		_preci	= 0,
				    unsigned		_chunk	= 0,
				    char		_seper	= 0,
				    char		_space	= 0 )
				    : ezcod_base( _lat, _lon )
				    , precision( _preci ? _preci : PRECISION )
				    , chunk( _chunk ? _chunk : CHUNK )
				    , separator( _seper )
				    , space( _space )
	{
	    if ( P < 1 )
		throw std::runtime_error( "ezpwd::ezcod:: At least one parity symbol must be specified" );
	    if ( precision < 1 || precision > bits.max_size() )
		throw std::runtime_error( std::string( "ezpwd::ezcod:: Only 1-" )
					  + std::to_string( bits.max_size() )
					  + " location symbol may be specified" );
	}
	explicit		ezcod(
				    const std::string  &_ezcod,
				    unsigned		_preci	= 0,
				    unsigned		_chunk	= 0,
				    char		_seper	= 0,
				    char		_space	= 0 )
				    : ezcod( 0, 0, _preci, _chunk, _seper, _space )
	{
	    decode( _ezcod );
	}
	virtual		       ~ezcod()
	{
	    ;
	}

	//
	// symbols	-- return working parity and location precision
	// 
	virtual ezcod_base::symbols_t
				symbols()
	    const
	{
	    return ezcod_base::symbols_t( P, precision );
	}

	virtual std::ostream   &output(
				    std::ostream       &lhs )
	    const
	{
	    std::streamsize	prec	= lhs.precision();
	    std::ios_base::fmtflags
				flg	= lhs.flags();
	    lhs.precision( 10 );
	    std::string		uni	= "m ";
	    double		acc	= accuracy;
	    double		dec	= 2;
	    if ( acc > 1000 ) {
		uni			= "km";
		acc		       /= 1000;
	    } else if ( acc < 1 ) {
		uni			= "mm";
		acc		       *= 1000;
	    }
	    if ( acc >= 100 )
		dec			= 0;
	    else if ( acc >= 10 )
		dec			= 1;

	    lhs << encode( precision )
		<< " ("		<< std::setw( 3 ) << confidence
		<< "%)  == "	<< std::showpos   << std::fixed << std::setprecision(  10 ) << std::setw( 15 ) << latitude
		<< ", "		<< std::showpos   << std::fixed << std::setprecision(  10 ) << std::setw( 15 ) << longitude
		<< " +/- "	<< std::noshowpos << std::fixed << std::setprecision( dec ) << std::setw(  6 ) << acc << uni;
	    lhs.precision( prec );
	    lhs.flags( flg );
	    return lhs;
	}

	// 
	// encode() -- encode the lat/lon to 'precision' symbols EZCOD representation
	// 
	virtual std::string	encode(
				    unsigned		_preci	= 0 )	// override precision
	    const
	{
	    // Convert lat/lon into a fraction of number of parts assigned to each
	    double		lat_frac= ( latitude +  90 ) / 180;
	    if ( lat_frac < 0 || lat_frac > 1 )
		throw std::runtime_error( "ezpwd::ezcod::encode: Latitude not in range [-90,90]" );
	    double		lon_frac= ( longitude + 180 ) / 360;
	    if ( lon_frac < 0 || lon_frac > 1 )
		throw std::runtime_error( "ezpwd::ezcod::encode: Longitude not in range [-180,180]" );
	    if ( _preci == 0 )
		_preci			= precision;
	    if ( _preci < 1 || _preci > bits.max_size() )
		throw std::runtime_error( std::string( "ezpwd::ezcod:: Only 1-" )
					  + std::to_string( bits.max_size() )
					  + " location symbol may be specified" );

	    // Compute the integer number of lat/lon parts represented by each coordinate, for the
	    // specified level of precision, and then truncate to the range [0,..._parts),
	    // eg. Latitude 90 --> 89.999...
	    uint32_t		lat_parts = parts[_preci-1].first;	// [ -90,90 ] / 4,194,304 parts in 9 symbols
	    uint32_t		lon_parts = parts[_preci-1].second;	// [-180,180] / 8,388,608 parts ''

	    uint32_t		lat_rem	= std::min( lat_parts-1, uint32_t( lat_parts * lat_frac ));
	    uint32_t		lon_rem	= std::min( lon_parts-1, uint32_t( lon_parts * lon_frac ));

	    // Initial loop condition; lat/lon multiplier is left at the base multiplier of the
	    // previous loop.  Then, loop computing the units multiplier, and hten removing the most
	    // significant bits (multiples of the units multiplier).  They will both reach 1
	    unsigned int	lat_mult= lat_parts;
	    unsigned int	lon_mult= lon_parts;

	    std::string		res;
	    res.reserve( _preci			// approximate result length
			 + ( chunk && chunk < _preci
			    ? _preci / chunk - 1
			    : 0 )
			 + 1 + P );
	    for ( auto &b : bits ) {
		unsigned char	lat_bits= b.first;
		unsigned char	lon_bits= b.second;
		lat_mult       	      >>= lat_bits;
		lon_mult       	      >>= lon_bits;
		if ( ! lat_mult || ! lon_mult )
		    break;

		// Each set of bits represents the number of times the current multiplier (after
		// division by the number of bits we're outputting) would go into the remainder.
		// Eg. If _mult was 1024, and _rem is 123 and _bits is 3, we're going to put out
		// the next 3 bits of the value 199.  The last value ended removing all multiples of
		// 1024.  So, we first get the new multiplier: 1024 >> 3 == 128.  So, we're
		// indicating, as a 3-bit value, how many multiples of 128 there are in the value
		// 199: 199 / 128 == 1, so the 3-bit value we output is 001
		uint32_t	lat_val	= lat_rem / lat_mult;
		lat_rem		       -= lat_val * lat_mult;

		uint32_t	lon_val	= lon_rem / lon_mult;
		lon_rem		       -= lon_val * lon_mult;

		res		       += char( ( lat_val << lon_bits ) | lon_val );
	    }

	    // Add the R-S parity symbols and base-32 encode, add parity separator and chunk
	    rscodec.encode( res );
	    serialize::base32::encode( res );
	    switch( separator ) {
	    case SEP_NONE:
		break;
	    case SEP_DOT: default:
		res.insert( _preci, 1, SEP_DOT );
		break;
	    case SEP_BANG:
	    case SEP_SPACE:
		res.insert( _preci, 1, separator );
		break;
	    }
	    if ( space != CHK_NONE && chunk && chunk < _preci ) {
		for ( unsigned c = _preci / chunk - 1; c > 0; --c ) {
		    switch ( space ) {
		    case CHK_NONE:
			break;
		    case CHK_SPACE: default:
			res.insert( c * chunk, 1, CHK_SPACE );
			break;
		    case CHK_DASH:
			res.insert( c * chunk, 1, space );
			break;
		    }
		}
	    }

	    return res;
	}

	// 
	// deserialize	-- Extract base-32, skip whitespace, mark invalid symbols as erasures
	// validate	-- Remove base-32 encoding, validate and remove parity, returning confidence
	// decode	-- Attempt to decode a lat/lon, returning the confidence percentage
	// 
	//     If data but no parity symbols are supplied, no error checking is performed, and the
	// confidence returned will be 0%.  No erasures within the supplied data are allowed (as
	// there is no capacity to correct them), and an exception will be thrown.
	// 
	//     If parity is supplied, then erasures are allowed.  So long as the total number of
	// erasures is <= the supplied parity symbols, then the decode will proceed (using the
	// parity symbols to fill in the erasures), and the returned confidence will reflect the
	// amount of unused parity capacity.  Each erasure consumes one parity symbol to repair.
	// 
	//     We'll allow question-mark or any of the slash characters: "_/\?" to indicate an
	// erasure.  Either of the "!."  symbol may be used to indicates the split between location
	// symbols and parity symbols, and must be in a position that corresponds to the indicated
	// number of location (this->precision) and parity 'P' symbols.  Whitespace symbols and dash
	// are ignored: " -".
	// 
	//     Thus, an EZCOD like "R3U 1JU QUY!0" may only be decoded by an ezcod<P=1>.  Without
	// the "!" or ".", it could be an ezcod<P=2> w/precision == 8 -- there's no way to know for
	// sure.  If no explicit position-parity separator is given, then we assume the default:
	// this->precision location symbols, then up to P parity symbols.  If additional parity
	// symbols are supplied after the separator, then However, an ezcod...<P=3>
	// 
	//     If an explicit "!" or "." separator IS provided, then we will attempt to decode the
	// position with the given number of position symbols, and up to P parity symbols.
	// 
	// NOTE
	// 
	//     Due to a perhaps unexpected feature of R-S codewords, a codeword with MORE parity
	// can be successfully decoded by an R-S codec specifying LESS parity symbols.  It so
	// happens that the data plus (excess) parity + (remaining) parity is STILL a valid codeword
	// (so long as the R-S Galois parameters are identical).
	// 
	//     Therefore, EZCODs with more parity are accepted by EZCOD parsers configured for less
	// parity.  Of course, they will have less error/erasure correction strength -- using the
	// correctly configured EZCOD codec expecting more R-S parity will maximize the value of all
	// the supplied parity.
	// 
	//    The full amount of parity (ie. everything after the location/parity separator) is
	// discarded in all cases, before the EZCOD location is decoded.
	// 
    private:

	unsigned		deserialize(
				    std::string	       &dec,
				    std::vector<int>   &erasure,
				    std::vector<char>  &invalid )
	    const
	{
	    serialize::base32::decode( dec, &erasure, &invalid );

	    // The special symbol '!' or '.' indicates the end of the EZCOD location symbols and the
	    // beginning of parity: ensure the symbol counts are consistent with the encoding.  By
	    // default the parity symbols begin at offset precision.  If we see more than precision
	    // symbols, we assume that the Lth and subsequent symbols are parity.  If a
	    // location/parity separator is provided, it must be at position this->precision!
	    // Return offset of start of parity in codeword.
	    unsigned		parbeg	= this->PRECISION;	// Parity begins after Location, by default
	    for ( unsigned i = 0; i < invalid.size(); ++i ) {
		switch ( invalid[i] ) {
		case '!': case '.':
		    // Remember the offset of the first parity symbol (it'll be in the position of
		    // the last '!' or '.' symbol we're about to erase), and adjust the indices of
		    // any erasures following.
		    parbeg		= erasure[i];
		    dec.erase( parbeg, 1 );
		    invalid.erase( invalid.begin() + i );
		    erasure.erase( erasure.begin() + i );
		    for ( unsigned j = i; j < erasure.size(); ++j )
			erasure[j]     -= 1;
		    break;
		case '_': case '/': case '\\': case '?':
		    break;
		default:
		    throw std::runtime_error( std::string( "ezpwd::ezcod::decode: invalid symbol presented: '" )
					      + invalid[i] + "'" );
		}
	    }
#if defined( DEBUG ) && DEBUG >= 1
	    std::cout << " --> 0x" << std::vector<uint8_t>( dec.begin(), dec.begin() + std::min( size_t( parbeg ), dec.length()) )
		      << " + 0x" << std::vector<uint8_t>( dec.begin() + std::min( size_t( parbeg ), dec.length()),
							  dec.begin() + dec.length() )
		      << " parity" << std::endl;
#endif
	    return parbeg;
	}

	int			validate(
				    std::string	       &dec )
	    const
	{
	    // Compute and return validity (which may later be assigned to this->confidence)
	    int			validity = 0;		// if no R-S parity provided

#if defined( DEBUG ) && DEBUG >= 1
	    std::cout << *this << " validate( " << dec << " ) ";
#endif
	    std::vector<int>	erasure;
	    std::vector<char>	invalid;
	    unsigned		parbeg	= deserialize( dec, erasure, invalid );

	    if ( dec.size() > parbeg || erasure.size() > 0 ) {
		// Some R-S parity symbol(s) were provided (or erasures were marked).  See if we can
		// successfully decode/correct, or (at least) use one parity symbol as a check
		// character.  If we identify more erasures than R-S parity, we must fail; we can't
		// recover the data.  This will of course be the case if we have *any* erasures in
		// the data, and no parity.
		unsigned	parity	= 0;
		if ( dec.size() > parbeg )
		    parity		= dec.size() - parbeg;
		while ( dec.size() < parbeg + P ) {
		    erasure.push_back( dec.size() );
		    dec.resize( dec.size() + 1 );
		}
#if defined( DEBUG ) && DEBUG >= 2
		std::cout << " --> erasures: " << erasure.size() << " vs. parity: " << parity
			  << ": " << std::vector<uint8_t>( dec.begin(), dec.end() ) << std::endl;
#endif
		if ( erasure.size() > parity ) {
		    // We cannot do R-S decoding; not enough parity symbols to even cover erasures.
		    // If parity symbol(s) were provided ('parity' > 0), and all erasures were due the
		    // missing remaining parity symbols, we can use the existing parity symbol(s) as
		    // "check character(s)", by simply re-encoding the supplied non-parity data, and
		    // see if the generated parity symbol(s) match the supplied parity.  This has
		    // basically the same strength as the 10:10 code's check character.
		    if ( parity + erasure.size() == P ) {
			// All erasures must be at end, in remaining parity symbols!
			std::string chk( dec.begin(), dec.begin() + parbeg );
			rscodec.encode( chk );
			// each parity symbol provided must match the corresponding encoded chk symbol
			for ( unsigned i = 0; i < parity; ++i )
			    if ( dec[parbeg+i] != chk[parbeg+i] )
				throw std::runtime_error( "ezpwd::ezcod::decode: Error correction failed; check character mismatch" );
			// Check character(s) matched; erasure.size()/P of confidence gone
			validity	= ezpwd::strength<P>( erasure.size(), erasure, erasure );
		    } else
			throw std::runtime_error( "ezpwd::ezcod::decode: Error correction failed; too many erasures" );
		} else {
		    // We can try R-S decoding; we have (at least) enough parity to try to recover
		    // any missing symbol(s).
		    std::vector<int>position;
		    int		corrects= rscodec.decode( dec, erasure, &position );
		    if ( corrects < 0 )
			throw std::runtime_error( "ezpwd::ezcod::decode: Error correction failed; R-S decode failed" );
		    // Compute confidence, from spare parity capacity.  Since R-S decode will not
		    // return the position of erasures that turn out (by accident) to be correct,
		    // but they have consumed parity capacity, we re-add them into the correction
		    // position vector.  If the R-S correction reports more corrections than the
		    // parity can possibly have handled correctly, (eg. 2 reported erasures and an
		    // unexpected error), then the decode is almost certainly incorrect; fail.
		    validity		= ezpwd::strength<P>( corrects, erasure, position );
		    if ( validity < 0 )
			throw std::runtime_error( "ezpwd::ezcod::decode: Error correction failed; R-S decode overwhelmed" );
		}
		if ( dec.size() > parbeg )
		    dec.resize( parbeg ); // Discard any parity symbols
	    }
	    return validity;
	}

    public:
	virtual int		decode( const std::string &str )
	{
	    // Decode the R-S encoding, computing the confidence.  Will raise an exception on any
	    // error.  Don't change this->confidence, this->latitude, ... until there is no longer a
	    // chance of exception.
	    std::string		decoded( str );
	    int			validity= validate( decoded );

	    // Unpack the supplied location data; we'll take as much as we are given, up to the
	    // maximum possible 12 symbols supported (9 symbols yielding ~3m resolution).
	    uint32_t		lat_tot	= 0;
	    uint32_t		lon_tot	= 0;

	    uint32_t		lat_mult= 1;
	    uint32_t		lon_mult= 1;

	    auto		di	= decoded.begin();
	    for ( auto &b : bits ) {
		if ( di == decoded.end() )
		    break;
		unsigned char	c	= *di++;

		unsigned char	lat_bits= b.first;
		unsigned char	lon_bits= b.second;

		uint32_t	lat_val	= c >> lon_bits;
		uint32_t	lon_val	= c & (( 1 << lon_bits ) - 1 );

		lat_mult	      <<= lat_bits;
		lat_tot		      <<= lat_bits;
		lat_tot		       += lat_val;

		lon_mult	      <<= lon_bits;
		lon_tot		      <<= lon_bits;
		lon_tot		       += lon_val;
	    }

	    // Convert the sum of lat/lon parts back into degrees, and round the (truncated) value
	    // to the middle of the error rectangle.  This allows us to minimize error even if we
	    // didn't have access to all of the origin symbols to decode.  The absolute error bar as
	    // a proportional factor [0,1) for lat/lon is at most the scale of the last parts
	    // multiplier used.  We'll use this later to compute the error in meters; for example,
	    // if the last value we added worked out to be worth units of 25m of the circumference,
	    // then we must now be within [0,25m) of the original point.
	    double		lat_err	= 1.0 / lat_mult;
	    double		lon_err	= 1.0 / lon_mult;
	    latitude			= 180 * ( double( lat_tot ) / lat_mult + lat_err / 2 ) -  90;
	    longitude			= 360 * ( double( lon_tot ) / lon_mult + lon_err / 2 ) - 180;

	    // Remember the decoded location precision for future encoding (overrides the default).
	    // Compute the certainty probability (0.0,1.0] given the number of parity symbols in
	    // excess: Given a base-32 symbol: 1 - 1 / ( 32 ^ P ) where P is the number of
	    // unconsumed parity.
	    precision			= decoded.size();
	    confidence			= validity;
	    certainty			= 0.0;
	    if ( PARITY * confidence / 100 )
		certainty		= 1.0 - 1.0 / std::pow( double( 32.0 ),
								double( PARITY * confidence / 100 ));

	    // Compute the resolution error (in m.) of the decoded lat/lon and compute the minimum
	    // accuracy -- the radius of the circle around the computed latitude/longitude, inside
	    // which the original latitude/longitude must have been.
	    // 
	    //            original   latitude error bar
	    //                 \     /
	    //                  o  - 
	    //                     |   longitude error bar
	    //                     |   /
	    //                  |--x--| 
	    //                    /|
	    //                   / |
	    //           computed  -
	    // 
	    // The maximum distance is the length of the diagonal of the error rectangle defined by
	    // 1/2 the latitude/longitude error bars.
	    // 
	    double		lon_circ= 1 * M_PI * 6371000;
	    double		lat_circ= 2 * M_PI * 6371000 * std::cos( latitude * M_PI / 180 );
	    latitude_error		= lat_err * lon_circ;
	    longitude_error		= lon_err * lat_circ;

	    accuracy			= sqrt(  latitude_error  / 2 * latitude_error  / 2
					       + longitude_error / 2 * longitude_error / 2 );
	    return confidence;
	}
    }; // class ezcod


    // 
    // ezcod::rscodec	-- Reed-Solomon parity codec
    // ezcod::bits	-- distribution of lat/lon precision in each code symbol
    // 
    //     Quickly establishes an extra bit of precision for Longitude, and then evenly distributes
    // future precision between lat/lon, always maintaining extra precision for Longitude.
    // 
    template < unsigned P, unsigned L >
    const ezpwd::RS<31,31-P>	ezcod<P,L>::rscodec;

    // Number of lat/lon bits represented for each location symbol
    template < unsigned P, unsigned L >
    const typename ezcod<P,L>::bits_t
    				ezcod<P,L>::bits = {
	{
	    //  bits per symbol                 lat lon
	    ezcod<P,L>::bits_t::value_type( 2,  3 ),
	    ezcod<P,L>::bits_t::value_type( 2,  3 ),
	    ezcod<P,L>::bits_t::value_type( 3,  2 ),
	    //                             --  --
	    //                              7   8
	    ezcod<P,L>::bits_t::value_type( 2,  3 ),
	    ezcod<P,L>::bits_t::value_type( 3,  2 ),
	    ezcod<P,L>::bits_t::value_type( 2,  3 ),
	    //                             --  --
	    //                             14  16
	    ezcod<P,L>::bits_t::value_type( 3,  2 ),
	    ezcod<P,L>::bits_t::value_type( 2,  3 ),
	    ezcod<P,L>::bits_t::value_type( 3,  2 ),
	    //                             --  --
	    //                             22  23
	    ezcod<P,L>::bits_t::value_type( 2,  3 ),
	    ezcod<P,L>::bits_t::value_type( 3,  2 ),
	    ezcod<P,L>::bits_t::value_type( 2,  3 ),
	    //                             --  --
	    //                             29  31
	}
    };

    // Total number of parts that lat/lon is subdivided into, for that number of location symbols.
    template < unsigned P, unsigned L >
    const typename ezcod<P,L>::parts_t
				ezcod<P,L>::parts = {
	{
	    //  parts per symbol                  lat parts    lon parts     lat lon bits
	    ezcod<P,L>::parts_t::value_type( 1UL <<  2,  1UL <<  3 ), // 2,  3
	    ezcod<P,L>::parts_t::value_type( 1UL <<  4,  1UL <<  6 ), // 2,  3
	    ezcod<P,L>::parts_t::value_type( 1UL <<  7,  1UL <<  8 ), // 3,  2
	    //                                                          --  --
	    //                                                           7   8
	    ezcod<P,L>::parts_t::value_type( 1UL <<  9,  1UL << 11 ), // 2,  3
	    ezcod<P,L>::parts_t::value_type( 1UL << 12,  1UL << 13 ), // 3,  2
	    ezcod<P,L>::parts_t::value_type( 1UL << 14,  1UL << 16 ), // 2,  3
	    //                                                          --  --
	    //                                                          14  16
	    ezcod<P,L>::parts_t::value_type( 1UL << 17,  1UL << 18 ), // 3,  2
	    ezcod<P,L>::parts_t::value_type( 1UL << 19,  1UL << 21 ), // 2,  3
	    ezcod<P,L>::parts_t::value_type( 1UL << 22,  1UL << 23 ), // 3,  2
	    //                                                          --  --
	    //                                                          22  23
	    ezcod<P,L>::parts_t::value_type( 1UL << 24,  1UL << 26 ), // 2,  3
	    ezcod<P,L>::parts_t::value_type( 1UL << 27,  1UL << 28 ), // 3,  2
	    ezcod<P,L>::parts_t::value_type( 1UL << 29,  1UL << 31 ), // 2,  3
	    //                                                          --  --
	    //                                                          29  31
	}
    };
} // namespace ezpwd

#endif // _EZPWD_EZCOD
