/*
------------------------------------------------------------------------------
MetaCam - Extract EXIF information from digital camera files, with
support for Vendor specific blocks.
Copyright (C) 2000 Daniel Stephens (daniel@cheeseplant.org)

This program 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 2
of the License, or (at your option) any later version.

This program 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.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
------------------------------------------------------------------------------
*/

#include <iostream>
#include <math.h>
#include <cstdio>
#include <ctype.h>

#include "metacam.h"
#include "dpyfuncs.h"

static const char *rcsid __attribute__((unused))="$Id: dpyfuncs.cc,v 1.14 2004/08/21 17:54:20 daniel Exp $";

void
dpyISO(OutputContext &ctx, const char *name, const IFDEntry &e, const void*)
{
    vector<tiffUNSIGNED> v = e.getUVALUE();
    ctx.startBlock(name) << v[1] << " (" << v[0] << ")";
    ctx.endBlock();
}

void
dpyString(OutputContext &ctx, const char *name, const IFDEntry &e, const void*)
{
    vector<string> v = e.getSTRING();
    ctx.startBlock(name) << v[0];
    ctx.endBlock();
}

void displayRational(OutputContext &ctx, const tiffRATIONAL &r)
{
    if (r.getNumerator() == 0) {
	ctx.os() << "0";
    } else if (r.getDenominator() == 1) {
	ctx.os() << r.getNumerator();
    } else {
	ctx.os() << r;
    }
}

void displayRational(OutputContext &ctx, const tiffSRATIONAL &r)
{
    if (r.getNumerator() == 0) {
	ctx.os() << "0";
    } else if (r.getDenominator() == 1) {
	ctx.os() << r.getNumerator();
    } else {
	ctx.os() << r;
    }
}

void
dpyLens(OutputContext &ctx, const char *name, const IFDEntry &e, const void*)
{
    vector<tiffRATIONAL> v = e.getRATIONAL();
    v[0] = v[0].normalize();
    v[1] = v[1].normalize();
    v[2] = v[2].normalize();
    v[3] = v[3].normalize();

    ctx.startBlock(name);
    if (v[0] == v[1]) {
	displayRational(ctx,v[0]);
	ctx.os() << "mm ";
    } else {
	displayRational(ctx,v[0]);
	ctx.os() << "-";
	displayRational(ctx,v[1]);
	ctx.os() << "mm ";
    }

    if (v[2] == v[3]) {
	ctx.os() << "f" << (double)v[2];
    } else {
	ctx.os() << "f" << (double)v[2];
	ctx.os() << "-";
	ctx.os() << "f" << (double)v[3];
    }

    ctx.endBlock();
}

void
dpyZoom(OutputContext &ctx, const char *name, const IFDEntry &e, const void*)
{
    vector<tiffRATIONAL> v = e.getRATIONAL();
    v[0] = v[0].normalize();

    ctx.startBlock(name);

//    displayRational(ctx,v[0]);
    double d = v[0];
    d *= 100.0;
    d = floor(d);
    d /= 100.0;
    
    ctx.os() << d << "mm";
    ctx.endBlock();
}

void
dpyExpAdjust(OutputContext &ctx, const char *name, const IFDEntry &e, const void*)
{
    vector<tiffSRATIONAL> v = e.getSRATIONAL();
    v[0] = v[0].normalize();

    ctx.startBlock(name);
    double d = v[0];
    d *= 1000.0;
    d = floor(d);
    d /= 1000.0;
    if (d > 0.0) {ctx.os() << "+";}
    ctx.os() << d << " EV";
    ctx.endBlock();
}

void
dpyShutter(OutputContext &ctx, const char *name, const IFDEntry &e, const void*)
{
    vector<tiffRATIONAL> v = e.getRATIONAL();
    v[0]=v[0].normalize();
 
    ctx.startBlock(name);
    displayRational(ctx,v[0]);
    ctx.os() << " Sec.";
    ctx.endBlock();
}

void
dpyAperture(OutputContext &ctx, const char *name, const IFDEntry &e, const void*)
{
    vector<tiffRATIONAL> v = e.getRATIONAL();

    ctx.startBlock(name) << "f" << (double)v[0];
    ctx.endBlock();
}

void
dpyPixels(OutputContext &ctx, const char *name, const IFDEntry &e, const void*)
{
    vector<tiffUNSIGNED> v = e.getUVALUE();

    ctx.startBlock(name) << v[0] << " pixels";
    ctx.endBlock();
}

void
dpySigned(OutputContext &ctx, const char *name, const IFDEntry &e, const void*)
{
    vector<tiffSIGNED> v = e.getSVALUE();

    ctx.startBlock(name);
    if (v[0] > 0) {
	ctx.os() << "+" << v[0];
    } else {
	ctx.os() << v[0];
    }
    ctx.endBlock();
}

void
dpyUnsigned(OutputContext &ctx, const char *name, const IFDEntry &e, const void*)
{
    vector<tiffUNSIGNED> v = e.getUVALUE();
    ctx.startBlock(name) << v[0];
    ctx.endBlock();
}

void
dpyResolution(OutputContext &ctx, const char *name, const IFDEntry &e, const void*)
{
    vector<tiffRATIONAL> v = e.getRATIONAL();
    v[0]=v[0].normalize();
    ctx.startBlock(name);
    displayRational(ctx,v[0]);
    ctx.os() << " Pixels/" << ctx.getContextValue("resolutionUnit");
    ctx.endBlock();
}

void
dpyNULL(OutputContext &ctx, const char *name, const IFDEntry &e, const void*)
{}

void
dpyYes(OutputContext &ctx, const char *name, const IFDEntry &e, const void*)
{
    ctx.startBlock(name) << "Yes";
    ctx.endBlock();
}

void
dpyResolutionType(OutputContext &ctx, const char *name, const IFDEntry &e, const void*)
{
    vector<tiffUNSIGNED> v = e.getUVALUE();
    const char *resolution_unit = "???";
    switch (v[0]) {
    case 1:
	resolution_unit = "???";
	break;
    case 2:
	resolution_unit = "Inch";
	break;
    case 3:
	resolution_unit = "Centimeter";
	break;
    }
    ctx.setContextValue("resolutionUnit", resolution_unit);
}

void
dpyBitsPerSample(OutputContext &ctx, const char *name, const IFDEntry &e, const void*)
{
    vector<tiffUNSIGNED> v = e.getUVALUE();
    ctx.startBlock(name) << "(";
    vector<tiffUNSIGNED>::iterator iter;
    bool first=true;
    for (iter = v.begin(); iter != v.end(); ++iter) {
	if (first) {
	    first=false;
	} else {
	    ctx.os() << ",";
	}
	ctx.os() << (*iter);
    }
    ctx.os() << ")";
    ctx.endBlock();
}

const char *findLookup(int key, const void *table) {
    if (table == 0) return 0;
    const lookupValue *ltable = static_cast<const lookupValue *>(table);
    /* Simplified test (Prohibits use of explicitly unknown values in
       lookups, but those would have been confusing anyway. */
    for (;ltable->value; ++ltable) {
	if (ltable->key == key) return ltable->value;
    }
    return 0;
}

void
dpyUnsignedLookup(OutputContext &ctx, const char *name, const IFDEntry &e, const void*tbl) {
    vector<tiffUNSIGNED> v = e.getUVALUE();
    ctx.startBlock(name);
    const char *val = findLookup((int)v[0], tbl);
    if (val) {
	ctx.os() << val;
    } else {
	ctx.os() << "Unknown (" << v[0] << ")";
    }
    ctx.endBlock();
}

void
dpyUndefinedLookup(OutputContext &ctx, const char *name, const IFDEntry &e, const void*tbl) {
    vector<unsigned char> v = e.getOPAQUE();
    ctx.startBlock(name);
    const char *val = findLookup((int)v[0], tbl);
    if (val) {
	ctx.os() << val;
    } else {
	ctx.os() << "Unknown (" << v[0] << ")";
    }
    ctx.endBlock();
}

void
dpyExifVersion(OutputContext &ctx, const char *name, const IFDEntry &e, const void*)
{
    vector<unsigned char> v = e.getOPAQUE();
    ctx.startBlock(name);
    vector<unsigned char>::iterator iter;
    for (iter=v.begin(); iter!=v.end(); ++iter) {
	unsigned char c = (*iter);
	if ((c < 32) || (c>126)) {c = ' ';}
	ctx.os() << c;
    }
    ctx.endBlock();
}

void
dpyExifAperture(OutputContext &ctx, const char *name, const IFDEntry &e, const void*)
{
    vector<tiffRATIONAL> v = e.getRATIONAL();
    ctx.startBlock(name);
    double a = v[0];
    double f = pow(M_SQRT2, a);
    f = f * 10.0;
    f = floor(f);
    f = f / 10.0;
    ctx.os() << "f" << f;
    ctx.endBlock();
}

void
dpyExifShutter(OutputContext &ctx, const char *name, const IFDEntry &e, const void*)
{
    vector<tiffSRATIONAL> v = e.getSRATIONAL();
    ctx.startBlock(name);
    double a = v[0];
    if (a > 0.0) {
	double f = pow(2.0, a);
	ctx.os() << "1/" << f << " Sec.";
    } else {
	double f = pow(2.0, -a);
	ctx.os() <<  f << " Sec.";
    }
    ctx.endBlock();
}

void
dpyRationalAsDouble(OutputContext &ctx, const char *name, const IFDEntry &e, const void *units)
{
    ctx.startBlock(name);

    if (e.getType() == tSRATIONAL) {
	vector<tiffSRATIONAL> v = e.getSRATIONAL();
	if (v[0].getDenominator() == 0) {
	    ctx.os() << "Infinity";
	} else {
	    double a = v[0];
	    ctx.os() << a;
	    if (units) { ctx.os() << (const char *) units; }
	}
    } else {
	vector<tiffRATIONAL> v = e.getRATIONAL();
	if (v[0].getDenominator() == 0) {
	    ctx.os() << "Infinity";
	} else {
	    double a = v[0];
	    ctx.os() << a;
	    if (units) { ctx.os() << (const char *) units; }
	}
    }

    ctx.endBlock();
}

void
dpyOlymSpecialMode(OutputContext &ctx, const char *name, const IFDEntry &e, const void*)
{
    vector<tiffUNSIGNED> v = e.getUVALUE();
    ctx.startBlock(name);
    switch (v[0]) {
    case 0: ctx.os() << "Normal"; break;
    case 1: ctx.os() << "Unknown"; break;
    case 2: ctx.os() << "Fast"; break;
    case 3: ctx.os() << "Panorama"; break;
    default:
	ctx.os() << "Unknown (" << v[0] << ")"; break;
    }
    ctx.os() << "; Seq " << v[1];
    if (v[0] == 3) {
	switch(v[2]) {
	case 1: ctx.os() << " Left -> Right"; break;
	case 2: ctx.os() << " Right -> Left"; break;
	case 3: ctx.os() << " Bottom -> Top"; break;
	case 4: ctx.os() << " Top -> Bottom"; break;
	default:
	    ctx.os() << " Unknown (" << v[2] << ")"; break;
	}
    }
    ctx.endBlock();
}

void
dpyOlymZoom(OutputContext &ctx, const char *name, const IFDEntry &e, const void*)
{
    vector<tiffRATIONAL> v = e.getRATIONAL();
    ctx.startBlock(name);
    double a = v[0];
    if (a == 0.0) {
	ctx.os() << "Normal";
	ctx.endBlock();
	return;
    }
    a = a * 100.0;
    a = floor(a);
    a = a / 100.0;
    ctx.os() << "x" << a;
    ctx.endBlock();
}


static void fmt(OutputContext &ctx, const char *prefix, const char *value)
{
    ctx.startBlock(prefix);
    ctx.os() << value;
    ctx.endBlock();
}


void
dpyCanonBlock1(OutputContext &ctx, const char *name, const IFDEntry &e, const void*)
{
    vector<tiffUNSIGNED> v = e.getUVALUE();

    try {
	int n = v[0] / 2;

	if (v[1])
	    fmt(ctx, "Macro Mode", v[1] == 1 ? "Macro" : "No Macro");

	if (v[2]) {
	    char tl[32];
	    snprintf(tl, sizeof tl, "%.1g", (double)v[2] / 10.0);
	    fmt(ctx, "Self Timer", tl);
	}

	if (v[4]) {
	    static char *flashmodes[] = {
		"Auto", "On", "Red-Eye Reduction", "Slow Synchro",
		"Auto + Red-Eye Reduction", "On + Red-Eye Reduction"
	    };
	    if (v[4] <= 6)
		fmt(ctx, "Flash Mode", flashmodes[v[4]]);
	    else if (v[4] == 16)
		fmt(ctx, "Flash Mode", "External");
	} else
	    fmt(ctx, "Flash Mode", "No Flash");

	fmt(ctx, "Drive Mode", v[5] ? "Continuous" : (v[2] ? "Timer" : "Normal"));

	if (n >= 32 && v[32]) {
	    fmt(ctx, "Focus Mode", "Continuous");
	} else if (v[7] <= 6) {
	    static char *focusModes[] = {
		"One-Shot", "AI Servo", "AI Focus", "Manual Focus", 
		"Single Focus", "Continuous Focus", "Manual Focus"
	    };
	    fmt(ctx, "Focus Mode", focusModes[v[7]]);
	}

	if (v[10] <= 2)	{
	    static char *imageSizes[] = {
		"Large", "Medium", "Small"
	    };
	    fmt(ctx, "Image Size", imageSizes[v[10]]);
	}

	{
	    static char *adjustment[] = { "Low", "Normal", "High", "unknown" };

	   fmt(ctx, "Contrast", adjustment[((short)v[13] + 1) & 0x3]);
	   fmt(ctx, "Saturation", adjustment[((short)v[14] + 1) & 0x3]);
	   fmt(ctx, "Sharpness", adjustment[((short)v[15] + 1) & 0x3]);
	}

	if (v[16] >= 15 && v[16] <= 19) {
	    static char *speeds[] = { "auto", "50", "100", "200", "400" };

	   fmt(ctx, "ISO Speed", speeds[v[16] - 15]);
	}

	if (v[17] >= 3 && v[17] <= 5) {
	    static char *meterModes[] = {
		"Evaluative", "Partial", "Center-weighted"
	    };
	   fmt(ctx, "Metering Mode", meterModes[v[17] - 3]);
	}

	if (v[18] >= 0x3000 && v[18] <= 0x3004) {
	    static char *afPoints[] = {
		"Manual Focus", "Auto-Select", "Right", "Center", "Left"
	    };
	   fmt(ctx, "Autofocus Point", afPoints[v[18] - 0x3000]);
	}

	if (v[20] <= 5) {
	    static char *exposureModes[] = {
		"Preprogrammed", "Program", "Tv-priority", "Av-priority",
		"Manual", "A-DEP"
	    };
	   fmt(ctx, "Exposure Mode", exposureModes[v[20]]);
	}
	if (v[20] == 0 && v[11] <= 11) {
	    static char *shootModes[] = {
		"Full Auto", "Manual", "Landscape", "Fast Shutter", "Slow Shutter",
		"Night", "B&W", "Sepia", "Portrait", "Sports", "Macro / Close-Up",
		"Pan Focus"
	    };
	   fmt(ctx, "Preprogrammed Mode", shootModes[v[11]]);
	}

	// printf("%d, %d, %d\n", v[23], v[24], v[25]); // DEBUG
	if (v[25] && v[23] && v[24]) {
	    double maxFocalLen = v[23];
	    double minFocalLen = v[24];
	    double focalFactor = (double)v[25];
	    char zoom[32];
	    if (maxFocalLen == minFocalLen) {
		snprintf(zoom, sizeof zoom, "%.1gmm", maxFocalLen / focalFactor);
	    } else {
		snprintf(zoom, sizeof zoom, "%g-%gmm", minFocalLen / focalFactor,
			 maxFocalLen / focalFactor);
	    }
	   fmt(ctx, "Focal Length", zoom);
	}

	if (v[29]) {
#define V29BIT(N) (v[29] & (1 << (N)))
	    string flashDetails;

	    if (V29BIT(14))
		flashDetails += ", External E-TTL";
	    if (V29BIT(13))
		flashDetails += ", Internal";
	    if (V29BIT(11))
		flashDetails += ", FP Sync";
	    if (V29BIT(4))
		flashDetails += ", FP Sync Enabled";

	    if (flashDetails != "")
		fmt(ctx, "Flash Details", flashDetails.data() + 2);
	}

    } catch (...) {
	return;
    }
}

extern void dpyCanonBlock4(OutputContext &ctx, const char *name, const IFDEntry &e, const void*)
{
    vector<tiffUNSIGNED> v = e.getUVALUE();

    try {	
//	int n = v[0] / 2;

	if (v[7] <= 6) {
	    static char *whiteBalances[] = {
		"Auto", "Sunny", "Cloudy", "Tungsten", "Fluorescent", "Flash", "Custom"
	    };
	   fmt(ctx, "White Balance", whiteBalances[v[7]]);
	}

	if (v[9] > 1) {
	    char n[32];

	    snprintf(n, sizeof n, "%u", (unsigned int)v[9]);
	   fmt(ctx, "Burst Sequence Number", n);
	}

	{
	    struct { unsigned short value; char *bias; } flashBias[] = {
		{0xffc0, "-2 EV"}, {0xffcc, "-1.67 EV"}, {0xffd0, "-1.50 EV"},
		{0xffd4, "-1.33 EV"}, {0xffe0, "-1 EV"}, {0xffec, "-0.67 EV"},
		{0xfff0, "-0.50 EV"}, {0xfff4, "-0.33 EV"}, {0x0000, "0 EV"},
		{0x000c, "0.33 EV"}, {0x0010, "0.50 EV"}, {0x0014, "0.67 EV"},
		{0x0020, "1 EV"}, {0x002c,  "1.33 EV"}, {0x0030, "1.50 EV"},
		{0x0034, "1.67 EV"}, {0x0040, "2 EV"}, {0, NULL}
	    };
	    for (int fb = 0; flashBias[fb].bias != NULL; fb++) {
		if (v[15] == flashBias[fb].value) {
		   fmt(ctx, "Flash Exposure Bias", flashBias[fb].bias);
		    break;
		}
	    }
	}
	
    } catch (...) {
	return;
    }
}


extern void dpyCanonImageNumber(OutputContext &ctx, const char *name, const IFDEntry &e, const void*)
{
    vector<tiffUNSIGNED> v = e.getUVALUE();

    try {
	unsigned long n = v[0];

	char img[32];
	snprintf(img, sizeof img, "%03lu-%04lu", n / 10000, n % 10000);

	ctx.startBlock(name) << img;
	ctx.endBlock();

    } catch (...) {
	return;
    }
}




extern void dpyCanonSerialNumber(OutputContext &ctx, const char *name, const IFDEntry &e, const void*)
{
    vector<tiffUNSIGNED> v = e.getUVALUE();

    try {
	unsigned long n = v[0];

	char sn[32];
	snprintf(sn, sizeof sn, "%04lX%05lu", n >> 16, n & 0xffff);
	
	fmt(ctx, "Camera Serial Number", sn);
    } catch (...) {
	return;
    }
}

extern void dpyCasioDistance(OutputContext &ctx, const char *name, const IFDEntry &e, const void*)
{
    vector<tiffUNSIGNED> v = e.getUVALUE();
    
    const char* units = "mm";
    double dist = double(v[0]);

    if (dist > 1000.0)
    {
	    dist /= 1000.0;
	    units = "m";
    }
    else if (dist > 10)
    {
	    dist /= 10;
	    units = "cm";
    }

    ctx.startBlock(name) << dist << units;
    ctx.endBlock();
}

extern void dpyExifFlash(OutputContext &ctx, const char *name, const IFDEntry &e, const void*)
{
    ctx.startBlock(name);
    vector<tiffUNSIGNED> v = e.getUVALUE();
    
    if (v[0] & 0x01) {
	ctx.os() << "Flash Fired; ";
    } else {
	ctx.os() << "Flash Not Fired; ";
    }
    if (v[0] & 0x04) {
	if (v[0] & 0x02) {
	    ctx.os() << "Return Light Not Detected; ";
	} else {
	    ctx.os() << "Return Light Detected; ";
	}
    }
    switch (v[0] & 0x18) {
    case 0x00: break;
    case 0x08: ctx.os() << "Compulsory Flash Firing; "; break;
    case 0x10: ctx.os() << "Compulsory Flash Suppression; "; break;
    case 0x18: ctx.os() << "Auto Mode; "; break;
    }

    if (v[0] & 0x20) {
	ctx.os() << "No Flash Function; ";
    }

    if (v[0] & 0x40) {
	ctx.os() << "Red-Eye Reduction Supported; ";
    }

    ctx.endBlock();
}


extern void dpyExifComponentConfiguration(OutputContext &ctx, const char *name, const IFDEntry &e, const void*)
{
    ctx.startBlock(name);
    vector<unsigned char> v = e.getOPAQUE();

    for (unsigned int i=0; i<v.size(); ++i) {
	switch (v[i]) {
	case 0: break;
	case 1: ctx.os() << "Y"; break;
	case 2: ctx.os() << "Cb"; break;
	case 3: ctx.os() << "Cr"; break;
	case 4: ctx.os() << "R"; break;
	case 5: ctx.os() << "G"; break;
	case 6: ctx.os() << "B"; break;
	default: ctx.os() << "Unknown(" << v[i] << ")"; break;
	}
    }
    
    ctx.endBlock();
}


extern void dpyZoomRatio(OutputContext &ctx, const char *name, const IFDEntry &e, const void*)
{
    ctx.startBlock(name);

    if (e.getType() == tSRATIONAL) {
	vector<tiffSRATIONAL> v = e.getSRATIONAL();
	
	if (v[0].getNumerator() == 0) {
	    ctx.os() << "None";
	} else {
	    displayRational(ctx, v[0]);
	    ctx.os() << "x";
	}
    } else {
	vector<tiffRATIONAL> v = e.getRATIONAL();

	if (v[0].getNumerator() == 0) {
	    ctx.os() << "None";
	} else {
	    displayRational(ctx, v[0]);
	    ctx.os() << "x";
	}
    }

    ctx.endBlock();
}

extern void dpyRationalDistance(OutputContext &ctx, const char *name, const IFDEntry &e, const void * units)
{
    ctx.startBlock(name);

    if (e.getType() == tSRATIONAL) {
	vector<tiffSRATIONAL> v = e.getSRATIONAL();
	
	if (v[0].getNumerator() == 0) {
	    ctx.os() << "Unknown";
	} else if (v[0].getDenominator() == 0) {
	    ctx.os() << "Infinity";
	} else {
	    ctx.os() << (double)v[0];
	    if (units) { ctx.os() << (const char *) units; }
	}
    } else {
	vector<tiffRATIONAL> v = e.getRATIONAL();

	if (v[0].getNumerator() == 0) {
	    ctx.os() << "Unknown";
	} else if (v[0].getDenominator() == 0) {
	    ctx.os() << "Infinity";
	} else {
	    ctx.os() << (double)v[0];
	    if (units) { ctx.os() << (const char *) units; }
	}
    }

    ctx.endBlock();
}

extern void dpy35mmFocal(OutputContext &ctx, const char *name, const IFDEntry &e, const void*)
{
    ctx.startBlock(name);

    vector<tiffUNSIGNED> v = e.getUVALUE();

    if (v[0] == 0) {
	ctx.os() << "Unknown";
    } else {
	ctx.os() << v[0] << "mm";
    }

    ctx.endBlock();
}

void
dpyNikonFocusPosition(OutputContext &ctx, const char *name, const IFDEntry &e, const void*) {
    vector<unsigned char> v = e.getOPAQUE();
    ctx.startBlock(name);
    const char *val = findLookup((int)v[1], lookFocusPosition);
    if (val) {
	ctx.os() << val;
    } else {
	ctx.os() << "Unknown (" << v[1] << ")";
    }
    ctx.endBlock();
}

void
dpyTypedComment(OutputContext &ctx, const char *name, const IFDEntry &e, const void*) {
    vector<unsigned char> v = e.getOPAQUE();
    ctx.startBlock(name);

    // The first 8 bytes specify the type
    char typecode[9];
    for (unsigned int i = 0; i < 8; ++i) {
	if (i < v.size()) {
	    typecode[i] = v[i];
	} else {
	    typecode[i] = 0;
	}
    }
    typecode[8]=0;

    if ((strcmp(typecode,"ASCII") == 0) || (typecode[0] == 0)) {
	for (unsigned int i = 8; i < v.size(); ++i) {
	    if (isprint(v[i])) {
		ctx.os() << v[i];
	    } else {
		ctx.os() << ".";
	    }
	}
    } else {
	ctx.os() << "Unknown encoding (" << typecode << ")";
    }
    
    ctx.endBlock();
}

void
dpyUndefinedString(OutputContext &ctx, const char *name, const IFDEntry &e, const void*) {
    vector<unsigned char> v = e.getOPAQUE();
    ctx.startBlock(name);

    for (unsigned int i = 0; i < v.size(); ++i) {
	if (v[i] == 0) {
	    ctx.os() << ' ';
	} else if (isprint(v[i])) {
	    ctx.os() << v[i];
	} else {
	    ctx.os() << ".";
	}
    }
    
    ctx.endBlock();
}

// Nikkor Lens display method provided by Ro Ro
// Updates for consistency with other modules by Daniel

void
dpyNikkorLensInfo(OutputContext &ctx, const char *name, 
		  const IFDEntry &e, const void*) {
    vector<unsigned char> v = e.getOPAQUE();
    ctx.startBlock(name);

    double d;
    if ((v[0x13] + v[0x14] + v[0x15] + v[0x16] + v[0x17])!=0) {
	// should be 'D'

	d = v[0x09]; // cm = 10^(x/40)
	d /= 40.0;
	d = pow(10,d);
	ctx.os() << "Distance: " << d << "cm; ";
    }
    
    d = v[0x12];  // calc k value - better use the tables from Ken Hancock
    d /= 24.0;
    d = pow(2,d);
    ctx.os() << "Effective Aperture Open: " << d << "; ";

    d = v[0x12]+v[0x0C];
    d /= 24.0;
    d = pow(2,d);
    ctx.os() << "Effective Aperture Stopped Down: " << d << "; ";

    unsigned int c;
    c =  v[0x0B];
    if (c<16){
	ctx.os() << "Lens ID = 0x0" << hex << c << dec << "; ";
    } else {
	ctx.os() << "Lens ID = 0x" << hex << c << dec << "; ";
    }

    c =  v[0x11];
    if (c<16) {
	ctx.os() << "Matrix Chip ID = 0x0" << hex << c<< dec << "; "; 
    } else {
	ctx.os() << "Matrix Chip ID = 0x" << hex << c << dec << ";";
    }

//	  76 - 0B - Lens ID
//	  7A - 11 - Lens Chip Number

    ctx.endBlock();
/*

---------------------------------------------------------------------------------------
****   following documents by
* Copyright (c) 1997 Ken Hancock <kenh@kagi.com>
* This document resides at http://members.aol.com/khancock/pilot/nbuddy/
****   provide infos about nikkor lenses

- N90s-memory-map.txt
0xFE29            Aperature (zoomed setting ?)
0xFE2D            Focus distance cm = 10^(x/40)
0xFE2E            Zoom Position (SEE TABLES)
0xFE2F            Lens Focal Length Range(Lo) (SEE kFocalTable)
0xFE30            Lens Focal Length Range(Hi) (SEE kFocalTable)
0xFE31            Lens Apperature Range(Lo) (SEE kCameraApertureTable)
0xFE32            Lens Apperature Range(Hi) (SEE kCameraApertureTable)
0xFE33            Lens ID
0xFE51            Effective Aperture (SEE kApertureTable)

- NikonStringTables.c.txt
const char * kFocalTable[] = {
const char * kCameraApertureTable[] = {
---------------------------------------------------------------------------------------



* a N90s is useful for comparing data
* the Chip Number was guessed through luck:
    the AF 20mm/2.8 and the AF 35-70mm/3.3-4.5 use the same
    chip (NEC uPD7554G577) which is different wirded

* sample data for Nikkor 50mm/1.8D
[0x98-7]: 30 31 30 31 - "0101" ?? Revision 1.01 ??

          25 13 08 03 01 - ?? don't know

	  85 - 09 - Distance info if D-lens, but how to know?
	  50 - 0A - Zoom Position
	  76 - 0B - Lens ID
	  58 - 0C - Lens Apperature Stops
	  50 - 0D - Lens Focal Length Short
	  50 - 0E - Lens Focal Length Long
	  14 - 0F - Lens Apperature Short
	  14 - 10 - Lens Apperature Long
	  7A - 11 - Lens Chip Number
	  14 - 12 - Effective Aperture

	  17 42 33 43 06 - ?? only on D ??
	  00 00 00 00 00 02 02 - ?? don't know ?? constant

*/

}
