/* TODO: Use the shared version of this function in print_cert.c. */
static void print_line(CFStringRef line) {
    UInt8 buf[256];
    CFRange range = { .location = 0 };
    range.length = CFStringGetLength(line);
    while (range.length > 0) {
        CFIndex bytesUsed = 0;
        CFIndex converted = CFStringGetBytes(line, range, kCFStringEncodingUTF8, 0, false, buf, sizeof(buf), &bytesUsed);
        fwrite(buf, 1, bytesUsed, stdout);
        range.length -= converted;
        range.location += converted;
    }
    fputc('\n', stdout);
}

static void printPlist(CFArrayRef plist, CFIndex indent, CFIndex maxWidth) {
    CFIndex count = CFArrayGetCount(plist);
    CFIndex ix;
    for (ix = 0; ix < count ; ++ix) {
        CFDictionaryRef prop = (CFDictionaryRef)CFArrayGetValueAtIndex(plist,
            ix);
        CFStringRef pType = (CFStringRef)CFDictionaryGetValue(prop,
            kSecPropertyKeyType);
        CFStringRef label = (CFStringRef)CFDictionaryGetValue(prop,
            kSecPropertyKeyLabel);
        CFStringRef llabel = (CFStringRef)CFDictionaryGetValue(prop,
            kSecPropertyKeyLocalizedLabel);
        CFTypeRef value = (CFTypeRef)CFDictionaryGetValue(prop,
            kSecPropertyKeyValue);

        bool isSection = CFEqual(pType, kSecPropertyTypeSection);
        CFMutableStringRef line = CFStringCreateMutable(NULL, 0);
        CFIndex jx = 0;
        for (jx = 0; jx < indent; ++jx) {
            CFStringAppend(line, CFSTR("    "));
        }
        if (llabel) {
            CFStringAppend(line, llabel);
            if (!isSection) {
                for (jx = CFStringGetLength(llabel) + indent * 4;
                    jx < maxWidth; ++jx) {
                    CFStringAppend(line, CFSTR(" "));
                }
                CFStringAppend(line, CFSTR(" : "));
            }
        }
        if (CFEqual(pType, kSecPropertyTypeWarning)) {
            CFStringAppend(line, CFSTR("*WARNING* "));
            CFStringAppend(line, (CFStringRef)value);
        } else if (CFEqual(pType, kSecPropertyTypeError)) {
            CFStringAppend(line, CFSTR("*ERROR* "));
            CFStringAppend(line, (CFStringRef)value);
        } else if (CFEqual(pType, kSecPropertyTypeSuccess)) {
            CFStringAppend(line, CFSTR("*OK* "));
            CFStringAppend(line, (CFStringRef)value);
        } else if (CFEqual(pType, kSecPropertyTypeTitle)) {
            CFStringAppend(line, CFSTR("*"));
            CFStringAppend(line, (CFStringRef)value);
            CFStringAppend(line, CFSTR("*"));
        } else if (CFEqual(pType, kSecPropertyTypeSection)) {
        } else if (CFEqual(pType, kSecPropertyTypeData)) {
            CFDataRef data = (CFDataRef)value;
            CFIndex length = CFDataGetLength(data);
            if (length > 20)
                CFStringAppendFormat(line, NULL, CFSTR("[%" PRIdCFIndex " bytes] "), length);
            const UInt8 *bytes = CFDataGetBytePtr(data);
            for (jx = 0; jx < length; ++jx) {
                if (jx == 0)
                    CFStringAppendFormat(line, NULL, CFSTR("%02X"), bytes[jx]);
                else if (jx < 15 || length <= 20)
                    CFStringAppendFormat(line, NULL, CFSTR(" %02X"),
                        bytes[jx]);
                else {
                    CFStringAppend(line, CFSTR(" ..."));
                    break;
                }
            }
        } else if (CFEqual(pType, kSecPropertyTypeString)) {
            CFStringAppend(line, (CFStringRef)value);
        } else if (CFEqual(pType, kSecPropertyTypeDate)) {
            CFLocaleRef lc = CFLocaleCopyCurrent();
            CFDateFormatterRef df = CFDateFormatterCreate(NULL, lc,
                kCFDateFormatterFullStyle, kCFDateFormatterFullStyle);
            //CFTimeZoneRef tz = CFTimeZoneCreateWithName(NULL, CFSTR("GMT"), false);
            //CFDateFormatterSetProperty(df, kCFDateFormatterTimeZone, tz);
            //CFRelease(tz);
            CFDateRef date = (CFDateRef)value;
            CFStringRef ds = CFDateFormatterCreateStringWithDate(NULL, df,
                date);
            CFStringAppend(line, ds);
            CFRelease(ds);
            CFRelease(df);
            CFRelease(lc);
        } else if (CFEqual(pType, kSecPropertyTypeURL)) {
            CFURLRef url = (CFURLRef)value;
            CFStringAppend(line, CFSTR("<"));
            CFStringAppend(line, CFURLGetString(url));
            CFStringAppend(line, CFSTR(">"));
        } else {
            CFStringAppendFormat(line, NULL, CFSTR("*unknown type %@* = %@"),
            pType, value);
        }

		if (!isSection || label)
			print_line(line);
		CFRelease(line);
        if (isSection) {
            printPlist((CFArrayRef)value, indent + 1, maxWidth);
        }
    }
}
static void print_plist(CFArrayRef plist) {
    if (plist)
        printPlist(plist, 0, maxLabelWidth(plist, 0));
    else
        printf("NULL plist\n");
}
static void printPlist(CFArrayRef plist, CFIndex indent, CFIndex maxWidth) {
    CFIndex count = CFArrayGetCount(plist);
    CFIndex ix;
    for (ix = 0; ix < count ; ++ix) {
        CFDictionaryRef prop = (CFDictionaryRef)CFArrayGetValueAtIndex(plist,
            ix);
        CFStringRef pType = (CFStringRef)CFDictionaryGetValue(prop,
            kSecPropertyKeyType);
        CFStringRef label = (CFStringRef)CFDictionaryGetValue(prop,
            kSecPropertyKeyLabel);
        CFStringRef llabel = (CFStringRef)CFDictionaryGetValue(prop,
            kSecPropertyKeyLocalizedLabel);
        CFTypeRef value = (CFTypeRef)CFDictionaryGetValue(prop,
            kSecPropertyKeyValue);

        bool isSection = CFEqual(pType, kSecPropertyTypeSection);
        CFMutableStringRef line = CFStringCreateMutable(NULL, 0);
        CFIndex jx = 0;
        for (jx = 0; jx < indent; ++jx) {
            CFStringAppend(line, CFSTR("    "));
        }
        if (llabel) {
            CFStringAppend(line, llabel);
            if (!isSection) {
                for (jx = CFStringGetLength(llabel) + indent * 4;
                    jx < maxWidth; ++jx) {
                    CFStringAppend(line, CFSTR(" "));
                }
                CFStringAppend(line, CFSTR(" : "));
            }
        }
        if (CFEqual(pType, kSecPropertyTypeWarning)) {
            CFStringAppend(line, CFSTR("*WARNING* "));
            CFStringAppend(line, (CFStringRef)value);
        } else if (CFEqual(pType, kSecPropertyTypeError)) {
            CFStringAppend(line, CFSTR("*ERROR* "));
            CFStringAppend(line, (CFStringRef)value);
        } else if (CFEqual(pType, kSecPropertyTypeSuccess)) {
            CFStringAppend(line, CFSTR("*OK* "));
            CFStringAppend(line, (CFStringRef)value);
        } else if (CFEqual(pType, kSecPropertyTypeTitle)) {
            CFStringAppend(line, CFSTR("*"));
            CFStringAppend(line, (CFStringRef)value);
            CFStringAppend(line, CFSTR("*"));
        } else if (CFEqual(pType, kSecPropertyTypeSection)) {
        } else if (CFEqual(pType, kSecPropertyTypeData)) {
            CFDataRef data = (CFDataRef)value;
            CFIndex length = CFDataGetLength(data);
            if (length > 20)
                CFStringAppendFormat(line, NULL, CFSTR("[%d bytes] "), length);
            const UInt8 *bytes = CFDataGetBytePtr(data);
            for (jx = 0; jx < length; ++jx) {
                if (jx == 0)
                    CFStringAppendFormat(line, NULL, CFSTR("%02X"), bytes[jx]);
                else if (jx < 15 || length <= 20)
                    CFStringAppendFormat(line, NULL, CFSTR(" %02X"),
                        bytes[jx]);
                else {
                    CFStringAppend(line, CFSTR(" ..."));
                    break;
                }
            }
        } else if (CFEqual(pType, kSecPropertyTypeString)) {
            CFStringAppend(line, (CFStringRef)value);
        } else if (CFEqual(pType, kSecPropertyTypeDate)) {
            CFDateRef date = (CFDateRef)value;
            CFLocaleRef lc = CFLocaleCopyCurrent();
            CFDateFormatterRef df = CFDateFormatterCreate(NULL, lc, kCFDateFormatterMediumStyle, kCFDateFormatterLongStyle);
            CFStringRef ds;
            if (df) {
                CFTimeZoneRef tz = CFTimeZoneCreateWithTimeIntervalFromGMT(NULL, 0.0);
                CFDateFormatterSetProperty(df, kCFDateFormatterTimeZone, tz);
                CFRelease(tz);
                ds = CFDateFormatterCreateStringWithDate(NULL, df, date);
                CFRelease(df);
            } else {
                ds = CFStringCreateWithFormat(NULL, NULL, CFSTR("%g"), CFDateGetAbsoluteTime(date));
            }
            CFStringAppend(line, ds);
            CFRelease(ds);
            CFRelease(lc);
        } else if (CFEqual(pType, kSecPropertyTypeURL)) {
            CFURLRef url = (CFURLRef)value;
            CFStringAppend(line, CFSTR("<"));
            CFStringAppend(line, CFURLGetString(url));
            CFStringAppend(line, CFSTR(">"));
        } else {
            CFStringAppendFormat(line, NULL, CFSTR("*unknown type %@* = %@"),
            pType, value);
        }

		if (!isSection || label)
			print_line(line);
		CFRelease(line);
        if (isSection) {
            printPlist((CFArrayRef)value, indent + 1, maxWidth);
        }
    }
}