bool RewriteSystem::isValidRefinementPath(
    const llvm::SmallVectorImpl<unsigned> &path) const {
  for (unsigned ruleID : path) {
    if (!getRule(ruleID).isProtocolRefinementRule())
      return false;
  }

  return true;
}

//===--- GeneratingConformances.cpp - Reasoning about conformance rules ---===//
// This source file is part of the Swift.org open source project
// See http://example.com for the list of Swift project authors
// This file implements an algorithm to find a minimal set of "generating
// conformances", which are rules (V1.[P1] => V1), ..., (Vn.[Pn] => Vn) such
// that any valid term of the form T.[P] can be written as a product of terms
// (Vi.[Pi]), where each Vi.[Pi] is a left hand side of a generating
// A "conformance-valid" rewrite system is one where if we can write
// T == U.V for arbitrary non-empty U and V, then U.[domain(V)] is joinable
// with U.
// If this holds, then starting with a term T.[P] that is joinable with T, we
// can reduce T to canonical form T', and find the unique rule (V.[P] => V) such
// that T' == U.V. Then we repeat this process with U.[domain(V)], which is
// known to be joinable with U, since T is conformance-valid.
// Iterating this process produces a decomposition of T.[P] as a product of
// left hand sides of conformance rules. Some of those rules are not minimal;
// they are added by completion, or they are redundant rules written by the
// Using the rewrite loops that generate the homotopy relation on rewrite paths,
// decompositions can be found for all "derived" conformance rules, producing
// a minimal set of generating conformances.
// There are two small complications to handle implementation details of
// Swift generics:
// 1) Inherited witness tables must be derivable by following other protocol
//    refinement requirements only, without looking at non-Self associated
//    types. This is expressed by saying that the generating conformance
//    equations for a protocol refinement can only be written in terms of
//    other protocol refinements; conformance paths involving non-Self
//    associated types are not considered.
// 2) The subject type of each conformance requirement must be derivable at
//    runtime as well, so for each generating conformance, it must be
//    possible to write down a conformance path for the parent type without
//    using any generating conformance recursively in the parent path of
// The generating conformances finds fewer conformance requirements to be
// redundant than homotopy reduction, which is why homotopy reduction only
// deletes non-protocol conformance requirements.

#include "swift/AST/Decl.h"
#include "swift/Basic/Defer.h"
#include "swift/Basic/Range.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/raw_ostream.h"
#include <algorithm>
#include "RewriteSystem.h"

using namespace swift;
using namespace rewriting;

/// Finds all protocol conformance rules appearing in a rewrite loop, both
/// in empty context, and with a non-empty left context. Applications of rules
/// with a non-empty right context are ignored.
/// The rules are organized by protocol. For each protocol, the first element
/// of the pair stores conformance rules that appear without context. The
/// second element of the pair stores rules that appear with non-empty left
/// context. For each such rule, the left prefix is also stored alongside.
void RewriteLoop::findProtocolConformanceRules(
    llvm::SmallDenseMap<const ProtocolDecl *,
                        ProtocolConformanceRules, 2> &result,
    const RewriteSystem &system) const {

  auto redundantRules = findRulesAppearingOnceInEmptyContext(system);

  bool foundAny = false;
  for (unsigned ruleID : redundantRules) {
    const auto &rule = system.getRule(ruleID);

    if (auto *proto = rule.isProtocolConformanceRule()) {
      if (rule.isIdentityConformanceRule()) {
        result[proto].SawIdentityConformance = true;
        continue;
      }

      result[proto].RulesInEmptyContext.push_back(ruleID);
      foundAny = true;
    }
  }

  if (!foundAny)
    return;

  RewritePathEvaluator evaluator(Basepoint);

  // Now look for rewrite steps with conformance rules in empty right context,
  // that is something like X.(Y.[P] => Y) (or it's inverse, X.(Y => Y.[P])).
  for (const auto &step : Path) {
    if (!evaluator.isInContext()) {
      switch (step.Kind) {
      case RewriteStep::ApplyRewriteRule: {
        const auto &rule = system.getRule(step.RuleID);

        if (rule.isIdentityConformanceRule())
          break;

        if (auto *proto = rule.isProtocolConformanceRule()) {
          if (step.StartOffset > 0 &&
              step.EndOffset == 0) {
            // Record the prefix term that is left unchanged by this rewrite step.
            // In the above example where the rewrite step is X.(Y.[P] => Z),
            // the prefix term is 'X'.
            const auto &term = evaluator.getCurrentTerm();
            MutableTerm prefix(term.begin(), term.begin() + step.StartOffset);
            result[proto].RulesInContext.emplace_back(prefix, step.RuleID);
          }
        }

        break;
      }

      case RewriteStep::AdjustConcreteType:
      case RewriteStep::Shift:
      case RewriteStep::Decompose:
        break;
      }
    }

    step.apply(evaluator, system);
  }
}

/// Write the term as a product of left hand sides of protocol conformance
/// The term should be irreducible, except for a protocol symbol at the end.
void
RewriteSystem::decomposeTermIntoConformanceRuleLeftHandSides(
    MutableTerm term, SmallVectorImpl<unsigned> &result) const {
  assert(term.back().getKind() == Symbol::Kind::Protocol);

  // If T is canonical and T.[P] => T, then by confluence, T.[P]
  // reduces to T in a single step, via a rule V.[P] => V, where
  RewritePath steps;
  bool simplified = simplify(term, &steps);
  if (!simplified) {
    llvm::errs() << "Term does not conform to protocol: " << term << "\n";
    abort();
  }

  assert(steps.size() == 1 &&
         "Canonical conformance term should simplify in one step");

  const auto &step = *steps.begin();

#ifndef NDEBUG
  const auto &rule = getRule(step.RuleID);
  assert(rule.isProtocolConformanceRule());
  assert(!rule.isIdentityConformanceRule());
#endif

  assert(step.Kind == RewriteStep::ApplyRewriteRule);
  assert(step.EndOffset == 0);
  assert(!step.Inverse);

  // If |U| > 0, recurse with the term U.[domain(V)]. Since T is
  // canonical, we know that U is canonical as well.
  if (step.StartOffset > 0) {
    // Build the term U.
    MutableTerm prefix(term.begin(), term.begin() + step.StartOffset);

    decomposeTermIntoConformanceRuleLeftHandSides(prefix, step.RuleID, result);
  } else {
    result.push_back(step.RuleID);
  }
}

/// Given a term U and a rule (V.[P] => V), write U.[domain(V)] as a
/// product of left hand sdies of conformance rules. The term U should
void
RewriteSystem::decomposeTermIntoConformanceRuleLeftHandSides(
    MutableTerm term, unsigned ruleID,
    SmallVectorImpl<unsigned> &result) const {
  const auto &rule = getRule(ruleID);
  assert(rule.isProtocolConformanceRule());
  assert(!rule.isIdentityConformanceRule());

  const auto &lhs = rule.getLHS();
  auto protocols = lhs[0].getProtocols();
  assert(protocols.size() == 1);
  auto protocol = Symbol::forProtocol(protocols[0], Context);

  // A same-type requirement of the form 'Self.Foo == Self' can induce a
  // conformance rule [P].[P] => [P], and we can end up with a generating
  // conformance decomposition of the form
  // where domain(V) == [P]. Don't recurse on [P].[P] here since it won't
  // yield anything useful, instead just return with (V'.[Q] => V').
  if (term.size() == 1 && term[0] == protocol) {
    result.push_back(ruleID);
    return;
  }

  // Build the term U.[domain(V)].
  term.add(protocol);

  decomposeTermIntoConformanceRuleLeftHandSides(term, result);

  // Add the rule V => V.[P].
  result.push_back(ruleID);
}

/// Use homotopy information to discover all ways of writing the left hand side
/// of each conformance rule as a product of left hand sides of other conformance
/// Each conformance rule (Vi.[P] => Vi) can always be written in terms of itself,
/// so the first term of each disjunction is always (Vi.[P] => Vi).
/// Conformance rules can also be circular, so not every choice of disjunctions
/// produces a valid result; for example, if you have these definitions:
///     associatedtype T : P
/// We have three conformance rules:
/// The first rule, <X>.[P] => <X> has an alternate conformance path:
/// The second rule similarly has an alternate conformance path:
/// This gives us the following initial set of candidate conformance paths:
/// One valid solution is the following set of assignments:
/// That is, we can choose to eliminate <X>.[P], but not <Y>.[P], or vice
/// versa; but it is never valid to eliminate both.
void RewriteSystem::computeCandidateConformancePaths(
    llvm::MapVector<unsigned,
                    std::vector<SmallVector<unsigned, 2>>> &conformancePaths) const {
  for (const auto &loop : Loops) {
    if (loop.isDeleted())
      continue;

    llvm::SmallDenseMap<const ProtocolDecl *,
                        ProtocolConformanceRules, 2> result;

    loop.findProtocolConformanceRules(result, *this);

    if (result.empty())
      continue;

    if (Debug.contains(DebugFlags::GeneratingConformances)) {
      llvm::dbgs() << "Candidate homotopy generator: ";
      loop.dump(llvm::dbgs(), *this);
      llvm::dbgs() << "\n";
    }

    for (const auto &pair : result) {
      const auto *proto = pair.first;
      const auto &inEmptyContext = pair.second.RulesInEmptyContext;
      const auto &inContext = pair.second.RulesInContext;
      bool sawIdentityConformance = pair.second.SawIdentityConformance;

      // No rules appear without context.
      if (inEmptyContext.empty())
        continue;

      if (Debug.contains(DebugFlags::GeneratingConformances)) {
        llvm::dbgs() << "* Protocol " << proto->getName() << ":\n";
        llvm::dbgs() << "** Conformance rules not in context:\n";
        for (unsigned ruleID : inEmptyContext) {
          llvm::dbgs() << "-- (#" << ruleID << ") " << getRule(ruleID) << "\n";
        }

        llvm::dbgs() << "** Conformance rules in context:\n";
        for (auto pair : inContext) {
          llvm::dbgs() << "-- " << pair.first;
          unsigned ruleID = pair.second;
          llvm::dbgs() << " (#" << ruleID << ") " << getRule(ruleID) << "\n";
        }

        if (sawIdentityConformance) {
          llvm::dbgs() << "** Equivalent to identity conformance\n";
        }

        llvm::dbgs() << "\n";
      }

      // Two conformance rules in empty context (T.[P] => T) and (T'.[P] => T)
      // are interchangeable, and contribute a trivial pair of conformance
      // equations expressing that each one can be written in terms of the
      for (unsigned candidateRuleID : inEmptyContext) {
        for (unsigned otherRuleID : inEmptyContext) {
          if (otherRuleID == candidateRuleID)
            continue;

          SmallVector<unsigned, 2> path;
          path.push_back(otherRuleID);
          conformancePaths[candidateRuleID].push_back(path);
        }
      }

      // If a rewrite loop contains a conformance rule (T.[P] => T) together
      // with the identity conformance ([P].[P] => [P]), both in empty context,
      // the conformance rule (T.[P] => T) is equivalent to the *empty product*
      // of conformance rules; that is, it is trivially redundant.
      if (sawIdentityConformance) {
        for (unsigned candidateRuleID : inEmptyContext) {
          SmallVector<unsigned, 2> emptyPath;
          conformancePaths[candidateRuleID].push_back(emptyPath);
        }
      }

      // Suppose a rewrite loop contains a conformance rule (T.[P] => T) in
      // empty context, and a conformance rule (V.[P] => V) in non-empty left
      // context U.
      // The rewrite loop looks something like this:
      // We can decompose U into a product of conformance rules:
      // Note that (V1)...(Vn) is canonically equivalent to U.
      // Now, we can record a candidate decomposition of (T.[P] => T) as a
      // product of conformance rules:
      // Again, note that (V1)...(Vn).V is canonically equivalent to U.V,
      // and therefore T.
      for (auto pair : inContext) {
        // We have a term U, and a rule V.[P] => V.
        SmallVector<unsigned, 2> conformancePath;

        // Simplify U to get U'.
        MutableTerm term = pair.first;
        (void) simplify(term);

        // Write U'.[domain(V)] as a product of left hand sides of protocol
        // conformance rules.
        decomposeTermIntoConformanceRuleLeftHandSides(term, pair.second,
                                                      conformancePath);

        // This decomposition defines a conformance access path for each
        // conformance rule we saw in empty context.
        for (unsigned otherRuleID : inEmptyContext)
          conformancePaths[otherRuleID].push_back(conformancePath);
      }
    }
  }
}

/// Determines if \p path can be expressed without any of the conformance
/// rules appearing in \p redundantConformances, by possibly substituting
/// any occurrences of the redundant rules with alternate definitions
/// appearing in \p conformancePaths.
/// The \p conformancePaths map sends conformance rules to a list of
/// disjunctions, where each disjunction is a product of other conformance
bool RewriteSystem::isValidConformancePath(
    llvm::SmallDenseSet<unsigned, 4> &visited,
    llvm::DenseSet<unsigned> &redundantConformances,
    const llvm::SmallVectorImpl<unsigned> &path,
    const llvm::MapVector<unsigned, SmallVector<unsigned, 2>> &parentPaths,
    const llvm::MapVector<unsigned,
                          std::vector<SmallVector<unsigned, 2>>>
        &conformancePaths) const {
  for (unsigned ruleID : path) {
    if (visited.count(ruleID) > 0)
      return false;

    if (redundantConformances.count(ruleID)) {
      SWIFT_DEFER {
        visited.erase(ruleID);
      };
      visited.insert(ruleID);

      auto found = conformancePaths.find(ruleID);
      assert(found != conformancePaths.end());

      bool foundValidConformancePath = false;
      for (const auto &otherPath : found->second) {
        if (isValidConformancePath(visited, redundantConformances, otherPath,
                                   parentPaths, conformancePaths)) {
          foundValidConformancePath = true;
          break;
        }
      }

      if (!foundValidConformancePath)
        return false;
    }

    auto found = parentPaths.find(ruleID);
    if (found != parentPaths.end()) {
      SWIFT_DEFER {
        visited.erase(ruleID);
      };
      visited.insert(ruleID);

      // If 'req' is based on some other conformance requirement
      // `T.[P.]A : Q', we want to make sure that we have a
      if (!isValidConformancePath(visited, redundantConformances, found->second,
                                  parentPaths, conformancePaths)) {
        return false;
      }
    }
  }

  return true;
}

/// Rules of the form [P].[Q] => [P] encode protocol refinement and can only
/// be redundant if they're equivalent to a sequence of other protocol
/// This helps ensure that the inheritance clause of a protocol is complete
/// and correct, allowing name lookup to find associated types of inherited
/// protocols while building the protocol requirement signature.

void RewriteSystem::dumpConformancePath(
    llvm::raw_ostream &out,
    const SmallVectorImpl<unsigned> &path) const {
  if (path.empty()) {
    out << "1";
    return;
  }

  for (unsigned ruleID : path)
    out << "(" << getRule(ruleID).getLHS() << ")";
}

void RewriteSystem::dumpGeneratingConformanceEquation(
    llvm::raw_ostream &out,
    unsigned baseRuleID,
    const std::vector<SmallVector<unsigned, 2>> &paths) const {
  out << getRule(baseRuleID).getLHS() << " := ";

  bool first = true;
  for (const auto &path : paths) {
    if (!first)
      out << " ∨ ";
    else
      first = false;

    dumpConformancePath(out, path);
  }
}

void RewriteSystem::verifyGeneratingConformanceEquations(
    const llvm::MapVector<unsigned,
                          std::vector<SmallVector<unsigned, 2>>>
        &conformancePaths) const {
#ifndef NDEBUG
  for (const auto &pair : conformancePaths) {
    const auto &rule = getRule(pair.first);
    auto *proto = rule.getLHS().back().getProtocol();

    MutableTerm baseTerm(rule.getLHS());
    (void) simplify(baseTerm);

    for (const auto &path : pair.second) {
      if (path.empty())
        continue;

      const auto &otherRule = getRule(path.back());
      auto *otherProto = otherRule.getLHS().back().getProtocol();

      if (proto != otherProto) {
        llvm::errs() << "Invalid equation: ";
        dumpGeneratingConformanceEquation(llvm::errs(),
                                          pair.first, pair.second);
        llvm::errs() << "\n";
        llvm::errs() << "Mismatched conformance:\n";
        llvm::errs() << "Base rule: " << rule << "\n";
        llvm::errs() << "Final rule: " << otherRule << "\n\n";
        dump(llvm::errs());
        abort();
      }

      MutableTerm otherTerm;
      for (unsigned otherRuleID : path) {
        otherTerm.append(getRule(otherRuleID).getLHS());
      }

      (void) simplify(otherTerm);

      if (baseTerm != otherTerm) {
        llvm::errs() << "Invalid equation: ";
        llvm::errs() << "\n";
        dumpGeneratingConformanceEquation(llvm::errs(),
                                          pair.first, pair.second);
        llvm::errs() << "Invalid conformance path:\n";
        llvm::errs() << "Expected: " << baseTerm << "\n";
        llvm::errs() << "Got: " << otherTerm << "\n\n";
        dump(llvm::errs());
        abort();
      }
    }
  }
#endif
}

static const ProtocolDecl *getParentConformanceForTerm(Term lhs) {
  // The last element is a protocol symbol, because this is the left hand side
  // of a conformance rule.
  assert(lhs.back().getKind() == Symbol::Kind::Protocol);

  // The second to last symbol is either an associated type, protocol or generic
  // parameter symbol.
  assert(lhs.size() >= 2);

  auto parentSymbol = lhs[lhs.size() - 2];

  switch (parentSymbol.getKind()) {
  case Symbol::Kind::AssociatedType: {
    // In a conformance rule of the form [P:T].[Q] => [P:T], the parent type is
    if (lhs.size() == 2)
      return nullptr;

    // If we have a rule of the form X.[P:Y].[Q] => X.[P:Y] wih non-empty X,
    // then the parent type is X.[P].
    const auto protos = parentSymbol.getProtocols();
    assert(protos.size() == 1);

    return protos[0];
  }

  case Symbol::Kind::GenericParam:
  case Symbol::Kind::Protocol:
    // The parent type is trivial (either a generic parameter, or the protocol
    return nullptr;

  case Symbol::Kind::Name:
  case Symbol::Kind::Layout:
  case Symbol::Kind::Superclass:
  case Symbol::Kind::ConcreteType:
    break;
  }

  llvm_unreachable("Bad symbol kind");
}

/// Computes a minimal set of generating conformances, assuming that homotopy
/// reduction has already eliminated all redundant rewrite rules that are not
void RewriteSystem::computeGeneratingConformances(
    llvm::DenseSet<unsigned> &redundantConformances) {
  // All conformance rules, sorted by (isExplicit(), getLHS()), with non-explicit
  // rules with longer left hand sides coming first.
  // The idea here is that we want less canonical rules to be eliminated first,
  // but we prefer to eliminate non-explicit rules, in an attempt to keep protocol
  // conformance rules in the same protocol as they were originally defined in.
  SmallVector<unsigned, 4> conformanceRules;

  // Maps a conformance rule to a conformance path deriving the subject type's
  // base type. For example, consider the following conformance rule:
  // The subject type is T.[P:A].[Q:B]; in order to derive the metadata, we need
  // the witness table for T.[P:A] : [Q] first, by computing a conformance access
  // path for the term T.[P:A].[Q], known as the 'parent path'.
  llvm::MapVector<unsigned, SmallVector<unsigned, 2>> parentPaths;

  // Maps a conformance rule to a list of paths. Each path in the list is a unique
  // derivation of the conformance in terms of other conformance rules.
  llvm::MapVector<unsigned, std::vector<SmallVector<unsigned, 2>>> conformancePaths;

  // The set of conformance rules which are protocol refinements, that is rules of
  llvm::DenseSet<unsigned> protocolRefinements;

  // Prepare the initial set of equations.
  for (unsigned ruleID : indices(Rules)) {
    const auto &rule = getRule(ruleID);
    if (rule.isPermanent())
      continue;

    if (rule.isRedundant())
      continue;

    if (!rule.isProtocolConformanceRule())
      continue;

    conformanceRules.push_back(ruleID);

    // Initially, every non-redundant conformance rule can be expressed
    // as itself.
    SmallVector<unsigned, 2> path;
    path.push_back(ruleID);
    conformancePaths[ruleID].push_back(path);

    // Save protocol refinement relations in a side table.
    if (rule.isProtocolRefinementRule()) {
      protocolRefinements.insert(ruleID);
      continue;
    }

    auto lhs = rule.getLHS();

    // Record a parent path if the subject type itself requires a non-trivial
    // conformance path to derive.
    if (auto *parentProto = getParentConformanceForTerm(lhs)) {
      MutableTerm mutTerm(lhs.begin(), lhs.end() - 2);
      assert(!mutTerm.empty());

      bool simplified = simplify(mutTerm);
      assert(!simplified || rule.isSimplified());
      (void) simplified;

      mutTerm.add(Symbol::forProtocol(parentProto, Context));

      // Get a conformance path for X.[P] and record it.
      decomposeTermIntoConformanceRuleLeftHandSides(mutTerm, parentPaths[ruleID]);
    }
  }

  computeCandidateConformancePaths(conformancePaths);

  if (Debug.contains(DebugFlags::GeneratingConformances)) {
    llvm::dbgs() << "Initial set of equations:\n";
    for (const auto &pair : conformancePaths) {
      llvm::dbgs() << "- ";
      dumpGeneratingConformanceEquation(llvm::dbgs(),
                                        pair.first, pair.second);
      llvm::dbgs() << "\n";
    }

    llvm::dbgs() << "Parent paths:\n";
    for (const auto &pair : parentPaths) {
      llvm::dbgs() << "- " << getRule(pair.first).getLHS() << ": ";
      dumpConformancePath(llvm::dbgs(), pair.second);
      llvm::dbgs() << "\n";
    }
  }

  verifyGeneratingConformanceEquations(conformancePaths);

  // Sort the list of conformance rules in reverse order; we're going to try
  // to minimize away less canonical rules first.
  std::sort(conformanceRules.begin(), conformanceRules.end(),
            [&](unsigned lhs, unsigned rhs) -> bool {
              const auto &lhsRule = getRule(lhs);
              const auto &rhsRule = getRule(rhs);

              if (lhsRule.isExplicit() != rhsRule.isExplicit())
                return !lhsRule.isExplicit();

              return lhsRule.getLHS().compare(rhsRule.getLHS(), Context) > 0;
            });

  // Find a minimal set of generating conformances.
  for (unsigned ruleID : conformanceRules) {
    const auto &paths = conformancePaths[ruleID];

    bool isProtocolRefinement = protocolRefinements.count(ruleID) > 0;

    for (const auto &path : paths) {
      // Only consider a protocol refinement rule to be redundant if it is
      // witnessed by a composition of other protocol refinement rules.
      if (isProtocolRefinement && !isValidRefinementPath(path))
        continue;

      llvm::SmallDenseSet<unsigned, 4> visited;
      visited.insert(ruleID);

      if (isValidConformancePath(visited, redundantConformances, path,
                                 parentPaths, conformancePaths)) {
        redundantConformances.insert(ruleID);
        break;
      }
    }
  }

  // Check invariants.
#ifndef NDEBUG
  for (const auto &pair : conformancePaths) {
    if (redundantConformances.count(pair.first) > 0)
      continue;

    const auto &rule = getRule(pair.first);

    if (rule.isRedundant()) {
      llvm::errs() << "Generating conformance is redundant: ";
      llvm::errs() << rule << "\n\n";
      dump(llvm::errs());
      abort();
    }

    if (rule.getLHS().containsUnresolvedSymbols()) {
      llvm::errs() << "Generating conformance contains unresolved symbols: ";
      llvm::errs() << rule << "\n\n";
      dump(llvm::errs());
      abort();
    }
  }
#endif

  if (Debug.contains(DebugFlags::GeneratingConformances)) {
    llvm::dbgs() << "Generating conformances:\n";

    for (const auto &pair : conformancePaths) {
      if (redundantConformances.count(pair.first) > 0)
        continue;

      llvm::dbgs() << "- " << getRule(pair.first) << "\n";
    }
  }
}


switch (error)
            {
                case wxGIF_INVFORMAT:
                    wxLogError(_("GIF: error in GIF image format."));
                    break;
                case wxGIF_MEMERR:
                    wxLogError(_("GIF: not enough memory."));
                    break;
                default:
                    wxLogError(_("GIF: unknown error!!!"));
                    break;
            }

// Name:        src/common/imaggif.cpp
// Purpose:     wxGIFHandler
// Author:      Vaclav Slavik & Guillermo Rodriguez Garcia

// For compilers that support precompilation, includes "wx.h".
#include "wx/wxprec.h"

#ifdef __BORLANDC__
    #pragma hdrstop
#endif

#if wxUSE_IMAGE && wxUSE_GIF

#ifndef WX_PRECOMP
    #include "wx/intl.h"
    #include "wx/log.h"
#endif

#include "wx/imaggif.h"
#include "wx/gifdecod.h"
#include "wx/wfstream.h"

IMPLEMENT_DYNAMIC_CLASS(wxGIFHandler,wxImageHandler)

// wxGIFHandler

#if wxUSE_STREAMS

bool wxGIFHandler::LoadFile(wxImage *image, wxInputStream& stream,
                            bool verbose, int index)
{
    wxGIFDecoder *decod;
    wxGIFErrorCode error;
    bool ok = true;

    decod = new wxGIFDecoder();
    error = decod->LoadGIF(stream);

    if ((error != wxGIF_OK) && (error != wxGIF_TRUNCATED))
    {
        if (verbose)
        {

        }
        delete decod;
        return false;
    }

    if ((error == wxGIF_TRUNCATED) && verbose)
    {
        wxLogError(_("GIF: data stream seems to be truncated."));
        /* go on; image data is OK */
    }

    if (ok)
    {
        ok = decod->ConvertToImage(index != -1 ? (size_t)index : 0, image);
    }
    else
    {
        wxLogError(_("GIF: Invalid gif index."));
    }

    delete decod;

    return ok;
}

bool wxGIFHandler::SaveFile( wxImage * WXUNUSED(image),
                             wxOutputStream& WXUNUSED(stream), bool verbose )
{
    if (verbose)
        wxLogDebug(wxT("GIF: the handler is read-only!!"));

    return false;
}

bool wxGIFHandler::DoCanRead( wxInputStream& stream )
{
    wxGIFDecoder decod;
    return decod.CanRead(stream);
}

#endif  // wxUSE_STREAMS

#endif  // wxUSE_GIF


static int spaceint_interrupt(void)
{
	if (readinputport(2) & 1)	/* Coin */
		return nmi_interrupt();
	else return interrupt();
}

#include "../sndhrdw/z80bw.cpp"

/***************************************************************************

Midway?? Z80 board memory map (preliminary)

0000-1bff ROM
2000-23ff RAM
2400-3fff Video RAM  (also writes between 4000 and 4fff, but only to simplify
                      code, ie. doesn't do clipping when the spaceship scrolls in)

Games which are referenced by this driver:
------------------------------------------
Astro Invader (astinvad)
Kamikaze (kamikaze)
Space Intruder (spaceint)

I/O ports:
read:
08        IN0
09        IN1
0a        IN2

write:
04        sound
05        sound
07		  ???
0b		  ???

TODO:

- How many sets of controls are there on an upright machine?

***************************************************************************/

#include "driver.h"
#include "vidhrdw/generic.h"

int  astinvad_interrupt(void);

int invaders_vh_start(void);
void invaders_vh_stop(void);

void invadpt2_vh_convert_color_prom(unsigned char *palette, unsigned short *colortable,const unsigned char *color_prom);
WRITE_HANDLER( invaders_videoram_w );
void invaders_vh_screenrefresh(struct osd_bitmap *bitmap,int full_refresh);

WRITE_HANDLER( astinvad_sh_port_4_w );
WRITE_HANDLER( astinvad_sh_port_5_w );
void astinvad_sh_update(void);

void init_astinvad(void);
void init_spaceint(void);

extern struct Samplesinterface astinvad_samples_interface;

static struct MemoryReadAddress astinvad_readmem[] =
{
	{ 0x0000, 0x1bff, MRA_ROM },
	{ 0x1c00, 0x3fff, MRA_RAM },
	{ -1 }  /* end of table */
};

static struct MemoryWriteAddress astinvad_writemem[] =
{
	{ 0x0000, 0x1bff, MWA_ROM },
	{ 0x1c00, 0x23ff, MWA_RAM },
	{ 0x2400, 0x3fff, invaders_videoram_w, &videoram, &videoram_size },
	{ -1 }  /* end of table */
};

static struct IOReadPort astinvad_readport[] =
{
	{ 0x08, 0x08, input_port_0_r },
	{ 0x09, 0x09, input_port_1_r },
	{ 0x0a, 0x0a, input_port_2_r },
	{ -1 }  /* end of table */
};

static struct IOWritePort astinvad_writeport[] =
{
	{ 0x04, 0x04, astinvad_sh_port_4_w },
	{ 0x05, 0x05, astinvad_sh_port_5_w },
	{ -1 }  /* end of table */
};

INPUT_PORTS_START( astinvad )
	PORT_START      /* IN0 */
	PORT_BIT( 0x01, IP_ACTIVE_HIGH, IPT_COIN1 )
	PORT_BIT( 0x02, IP_ACTIVE_HIGH, IPT_START2 )
	PORT_BIT( 0x04, IP_ACTIVE_HIGH, IPT_START1 )
	PORT_BIT( 0x08, IP_ACTIVE_HIGH, IPT_UNKNOWN )
	PORT_BIT( 0x10, IP_ACTIVE_HIGH, IPT_BUTTON1 )
	PORT_BIT( 0x20, IP_ACTIVE_HIGH, IPT_JOYSTICK_LEFT | IPF_2WAY )
	PORT_BIT( 0x40, IP_ACTIVE_HIGH, IPT_JOYSTICK_RIGHT | IPF_2WAY )
	PORT_BIT( 0x80, IP_ACTIVE_LOW, IPT_UNKNOWN )

	PORT_START      /* IN1 */
	PORT_DIPNAME( 0x01, 0x00, DEF_STR( Lives ) )
	PORT_DIPSETTING(    0x00, "3" )
	PORT_DIPSETTING(    0x01, "4" )
	PORT_DIPNAME( 0x02, 0x02, DEF_STR( Bonus_Life ) )
	PORT_DIPSETTING(    0x02, "10000" )
	PORT_DIPSETTING(    0x00, "20000" )
	PORT_DIPNAME( 0x04, 0x00, DEF_STR( Unknown ) )
	PORT_DIPSETTING(    0x00, DEF_STR( Off ) )
	PORT_DIPSETTING(    0x04, DEF_STR( On ) )
	PORT_DIPNAME( 0x88, 0x00, DEF_STR( Coinage ) )
	PORT_DIPSETTING(    0x88, DEF_STR( 3C_1C ) )
	PORT_DIPSETTING(    0x80, DEF_STR( 2C_1C ) )
	PORT_DIPSETTING(    0x00, DEF_STR( 1C_1C ) )
	PORT_DIPSETTING(    0x08, DEF_STR( 1C_2C ) )
	PORT_BIT( 0x10, IP_ACTIVE_HIGH, IPT_BUTTON1 | IPF_PLAYER2 )
	PORT_BIT( 0x20, IP_ACTIVE_HIGH, IPT_JOYSTICK_LEFT | IPF_2WAY | IPF_PLAYER2 )
	PORT_BIT( 0x40, IP_ACTIVE_HIGH, IPT_JOYSTICK_RIGHT | IPF_2WAY | IPF_PLAYER2 )

	PORT_START      /* IN2 */
	PORT_DIPNAME( 0x01, 0x00, "Freeze" )
	PORT_DIPSETTING(    0x00, DEF_STR( Off ) )
	PORT_DIPSETTING(    0x01, DEF_STR( On ) )
	PORT_BIT( 0xfe, IP_ACTIVE_HIGH, IPT_UNKNOWN )

	PORT_START	/* FAKE - select cabinet type */
	PORT_DIPNAME( 0x01, 0x00, DEF_STR( Cabinet ) )
	PORT_DIPSETTING(    0x00, DEF_STR( Upright ) )
	PORT_DIPSETTING(    0x01, DEF_STR( Cocktail ) )
	PORT_BIT( 0xfe, IP_ACTIVE_HIGH, IPT_UNUSED )
INPUT_PORTS_END

INPUT_PORTS_START( kamikaze )
	PORT_START      /* IN0 */
	PORT_BIT( 0x01, IP_ACTIVE_HIGH, IPT_COIN1 )
	PORT_BIT( 0x02, IP_ACTIVE_HIGH, IPT_START2 )
	PORT_BIT( 0x04, IP_ACTIVE_HIGH, IPT_START1 )
	PORT_BIT( 0x08, IP_ACTIVE_HIGH, IPT_UNKNOWN )
	PORT_BIT( 0x10, IP_ACTIVE_HIGH, IPT_BUTTON1 )
	PORT_BIT( 0x20, IP_ACTIVE_HIGH, IPT_JOYSTICK_LEFT | IPF_2WAY )
	PORT_BIT( 0x40, IP_ACTIVE_HIGH, IPT_JOYSTICK_RIGHT | IPF_2WAY )
	PORT_BIT( 0x80, IP_ACTIVE_LOW, IPT_UNKNOWN )

	PORT_START      /* IN1 */
	PORT_DIPNAME( 0x03, 0x03, DEF_STR( Lives ) )
	PORT_DIPSETTING(    0x03, "3" )
	PORT_DIPSETTING(    0x01, "4" )
	PORT_DIPSETTING(    0x02, "5" )
	PORT_DIPSETTING(    0x00, "6" )
	PORT_DIPNAME( 0x04, 0x00, DEF_STR( Unknown ) )
	PORT_DIPSETTING(    0x00, DEF_STR( Off ) )
	PORT_DIPSETTING(    0x04, DEF_STR( On ) )
	PORT_DIPNAME( 0x88, 0x88, DEF_STR( Bonus_Life ) )
	PORT_DIPSETTING(    0x88, "5000" )
	PORT_DIPSETTING(    0x80, "10000" )
	PORT_DIPSETTING(    0x08, "15000" )
	PORT_DIPSETTING(    0x00, "20000" )
	PORT_BIT( 0x10, IP_ACTIVE_HIGH, IPT_BUTTON1 | IPF_PLAYER2 )
	PORT_BIT( 0x20, IP_ACTIVE_HIGH, IPT_JOYSTICK_LEFT | IPF_2WAY | IPF_PLAYER2 )
	PORT_BIT( 0x40, IP_ACTIVE_HIGH, IPT_JOYSTICK_RIGHT | IPF_2WAY | IPF_PLAYER2 )

	PORT_START      /* IN2 */
	PORT_DIPNAME( 0x01, 0x00, "Freeze" )
	PORT_DIPSETTING(    0x00, DEF_STR( Off ) )
	PORT_DIPSETTING(    0x01, DEF_STR( On ) )
	PORT_BIT( 0xfe, IP_ACTIVE_HIGH, IPT_UNKNOWN )

	PORT_START	/* FAKE - select cabinet type */
	PORT_DIPNAME( 0x01, 0x00, DEF_STR( Cabinet ) )
	PORT_DIPSETTING(    0x00, DEF_STR( Upright ) )
	PORT_DIPSETTING(    0x01, DEF_STR( Cocktail ) )
	PORT_BIT( 0xfe, IP_ACTIVE_HIGH, IPT_UNUSED )
INPUT_PORTS_END

static struct MachineDriver machine_driver_astinvad = /* LT */
{
	/* basic machine hardware */
	{
		{
			CPU_Z80,
			2000000,        /* 2 Mhz? */
			astinvad_readmem,astinvad_writemem,astinvad_readport,astinvad_writeport,
			interrupt,1    /* two interrupts per frame */
		}
	},
	60, DEFAULT_60HZ_VBLANK_DURATION,       /* frames per second, vblank duration */
	1,      /* single CPU, no need for interleaving */
	0,

	/* video hardware */
	32*8, 32*8, { 0*8, 32*8-1, 0*8, 28*8-1 },
	0,      /* no gfxdecodeinfo - bitmapped display */
	8, 0,
	invadpt2_vh_convert_color_prom,

	VIDEO_TYPE_RASTER | VIDEO_SUPPORTS_DIRTY | VIDEO_MODIFIES_PALETTE,
	0,
	invaders_vh_start,
	invaders_vh_stop,
	invaders_vh_screenrefresh,

	/* sound hardware */
	0, 0, 0, 0,
	{
		{
			SOUND_SAMPLES,
			&astinvad_samples_interface
		}
	}
};

/***************************************************************************

  Game driver(s)

***************************************************************************/

/*------------------------------------------------------------------------------
 Shoei Space Intruder
 Added By Lee Taylor (example@nnthink.com)
 December 1998
------------------------------------------------------------------------------*/

static struct MemoryReadAddress spaceint_readmem[] =
{
	{ 0x0000, 0x17ff, MRA_ROM },
	{ 0x2000, 0x23ff, MRA_RAM },
	{ 0x4000, 0x5fff, MRA_RAM },
	{ -1 }  /* end of table */
};

static struct MemoryWriteAddress spaceint_writemem[] =
{
	{ 0x0000, 0x17ff, MWA_ROM },
	{ 0x2000, 0x23ff, MWA_RAM },
	{ 0x4000, 0x40ff, MWA_RAM },
	{ 0x4100, 0x5fff, invaders_videoram_w, &videoram, &videoram_size },
	{ -1 }  /* end of table */
};

static struct IOReadPort spaceint_readport[] =
{
	{ 0x00, 0x00, input_port_0_r },
	{ 0x01, 0x01, input_port_1_r },
	{ -1 }  /* end of table */
};

static struct IOWritePort spaceint_writeport[] =
{
	{ -1 }  /* end of table */
};

INPUT_PORTS_START( spaceint )
	PORT_START      /* IN0 */
	PORT_BIT( 0x01, IP_ACTIVE_HIGH, IPT_START1 )
	PORT_BIT( 0x02, IP_ACTIVE_HIGH, IPT_START2  )
	PORT_BIT( 0x04, IP_ACTIVE_HIGH, IPT_JOYSTICK_RIGHT | IPF_2WAY)
	PORT_BIT( 0x08, IP_ACTIVE_HIGH, IPT_JOYSTICK_LEFT | IPF_2WAY  )
	PORT_BIT( 0x10, IP_ACTIVE_HIGH, IPT_BUTTON1 )
	PORT_BIT( 0x20, IP_ACTIVE_HIGH, IPT_JOYSTICK_RIGHT | IPF_2WAY | IPF_PLAYER2)
	PORT_BIT( 0x40, IP_ACTIVE_HIGH, IPT_JOYSTICK_LEFT | IPF_2WAY | IPF_PLAYER2)
	PORT_BIT( 0x80, IP_ACTIVE_HIGH, IPT_BUTTON1 | IPF_PLAYER2)

	PORT_START      /* IN1 */
	PORT_DIPNAME( 0x01, 0x00, DEF_STR( Unknown ) )
	PORT_DIPSETTING(    0x00, DEF_STR( Off ) )
	PORT_DIPSETTING(    0x01, DEF_STR( On ) )
	PORT_DIPNAME( 0x06, 0x00, DEF_STR( Lives ) )
	PORT_DIPSETTING(    0x00, "3" )
	PORT_DIPSETTING(    0x04, "4" )
	PORT_DIPSETTING(    0x02, "5" )
	PORT_DIPNAME( 0x08, 0x00, DEF_STR( Coinage ) )
	PORT_DIPSETTING(    0x00, DEF_STR( 1C_1C ) )
	PORT_DIPSETTING(    0x08, DEF_STR( 1C_2C ) )
	PORT_DIPNAME( 0x10, 0x00, DEF_STR( Unknown ) )
	PORT_DIPSETTING(    0x00, DEF_STR( Off ) )
	PORT_DIPSETTING(    0x10, DEF_STR( On ) )
	PORT_DIPNAME( 0x20, 0x00, DEF_STR( Unknown ) )
	PORT_DIPSETTING(    0x00, DEF_STR( Off ) )
	PORT_DIPSETTING(    0x20, DEF_STR( On ) )
	PORT_DIPNAME( 0x40, 0x00, DEF_STR( Unknown ) )
	PORT_DIPSETTING(    0x00, DEF_STR( Off ) )
	PORT_DIPSETTING(    0x40, DEF_STR( On ) )
	PORT_DIPNAME( 0x80, 0x00, DEF_STR( Unknown ) )
	PORT_DIPSETTING(    0x00, DEF_STR( Off ) )
	PORT_DIPSETTING(    0x80, DEF_STR( On ) )

	PORT_START	/* FAKE */
	/* The coin slots are not memory mapped. Coin insertion causes a NMI. */
	/* This fake input port is used by the interrupt */
	/* handler to be notified of coin insertions. We use IMPULSE to */
	/* trigger exactly one interrupt, without having to check when the */
	/* user releases the key. */
	PORT_BIT_IMPULSE( 0x01, IP_ACTIVE_HIGH, IPT_COIN1, 1 )

	PORT_START	/* FAKE - select cabinet type */
	PORT_DIPNAME( 0x01, 0x00, DEF_STR( Cabinet ) )
	PORT_DIPSETTING(    0x00, DEF_STR( Upright ) )
	PORT_DIPSETTING(    0x01, DEF_STR( Cocktail ) )
	PORT_BIT( 0xfe, IP_ACTIVE_HIGH, IPT_UNUSED )
INPUT_PORTS_END

static struct MachineDriver machine_driver_spaceint = /* 20-12-1998 LT */
{
	/* basic machine hardware */
	{
		{
			CPU_Z80,
			2000000,        /* 2 Mhz? */
			spaceint_readmem,spaceint_writemem,spaceint_readport,spaceint_writeport,
			spaceint_interrupt,1
		}
	},
	60, DEFAULT_60HZ_VBLANK_DURATION,       /* frames per second, vblank duration */
	1,      /* single CPU, no need for interleaving */
	0,

	/* video hardware */
	32*8, 32*8, { 0*8, 32*8-1, 0*8, 32*8-1 },
	0,      /* no gfxdecodeinfo - bitmapped display */
	8, 0,
	invadpt2_vh_convert_color_prom,

	VIDEO_TYPE_RASTER | VIDEO_SUPPORTS_DIRTY | VIDEO_MODIFIES_PALETTE,
	0,
	invaders_vh_start,
	invaders_vh_stop,
	invaders_vh_screenrefresh,

	/* sound hardware */
	0, 0, 0, 0,
	{
		{
			SOUND_SAMPLES,
			&astinvad_samples_interface
		}
	}
};

ROM_START( astinvad )
	ROM_REGION( 0x10000, REGION_CPU1 )     /* 64k for code */
	ROM_LOAD( "ai_cpu_1.rom", 0x0000, 0x0400, 0x20e3ec41 )
	ROM_LOAD( "ai_cpu_2.rom", 0x0400, 0x0400, 0xe8f1ab55 )
	ROM_LOAD( "ai_cpu_3.rom", 0x0800, 0x0400, 0xa0092553 )
	ROM_LOAD( "ai_cpu_4.rom", 0x0c00, 0x0400, 0xbe14185c )
	ROM_LOAD( "ai_cpu_5.rom", 0x1000, 0x0400, 0xfee681ec )
	ROM_LOAD( "ai_cpu_6.rom", 0x1400, 0x0400, 0xeb338863 )
	ROM_LOAD( "ai_cpu_7.rom", 0x1800, 0x0400, 0x16dcfea4 )

	ROM_REGION( 0x0400, REGION_PROMS )
	ROM_LOAD( "ai_vid_c.rom", 0x0000, 0x0400, 0xb45287ff )
ROM_END

ROM_START( kamikaze )
	ROM_REGION( 0x10000, REGION_CPU1 )     /* 64k for code */
	ROM_LOAD( "km01",         0x0000, 0x0800, 0x8aae7414 )
	ROM_LOAD( "km02",         0x0800, 0x0800, 0x6c7a2beb )
	ROM_LOAD( "km03",         0x1000, 0x0800, 0x3e8dedb6 )
	ROM_LOAD( "km04",         0x1800, 0x0800, 0x494e1f6d )

	ROM_REGION( 0x0400, REGION_PROMS )
	ROM_LOAD( "ai_vid_c.rom", 0x0000, 0x0400, BADCRC(0xb45287ff) )
ROM_END

ROM_START( spaceint )
	ROM_REGION( 0x10000, REGION_CPU1 )     /* 64k for code */
	ROM_LOAD( "1",	 0x0000, 0x0400, 0x184314d2 )
	ROM_LOAD( "2",	 0x0400, 0x0400, 0x55459aa1 )
	ROM_LOAD( "3",	 0x0800, 0x0400, 0x9d6819be )
	ROM_LOAD( "4",	 0x0c00, 0x0400, 0x432052d4 )
	ROM_LOAD( "5",	 0x1000, 0x0400, 0xc6cfa650 )
	ROM_LOAD( "6",	 0x1400, 0x0400, 0xc7ccf40f )

	ROM_REGION( 0x0100, REGION_PROMS )
	ROM_LOAD( "clr", 0x0000, 0x0100, 0x13c1803f )
ROM_END

GAME( 1980, astinvad, 0,        astinvad, astinvad, astinvad, ROT270, "Stern", "Astro Invader" )
GAME( 1979, kamikaze, astinvad, astinvad, kamikaze, astinvad, ROT270, "Leijac Corporation", "Kamikaze" )
GAMEX(1980, spaceint, 0,        spaceint, spaceint, spaceint, ROT0,   "Shoei", "Space Intruder", GAME_WRONG_COLORS | GAME_NO_SOUND | GAME_NO_COCKTAIL )


const char* DLL_CALLCONV
FreeImage_TagToString(FREE_IMAGE_MDMODEL model, FITAG *tag, char *Make) {
	switch(model) {
		case FIMD_EXIF_MAIN:
		case FIMD_EXIF_EXIF:
			return ConvertExifTag(tag);

		case FIMD_EXIF_GPS:
			return ConvertExifGPSTag(tag);

		case FIMD_EXIF_MAKERNOTE:
			// We should use the Make string to select an appropriate conversion function
			break;

		case FIMD_EXIF_INTEROP:
		default:
			break;
	}

	return ConvertAnyTag(tag);
}

// Tag to string conversion functions
// Design and implementation by

#ifdef _MSC_VER
#pragma warning (disable : 4786) // identifier was truncated to 'number' characters
#endif

#include "FreeImage.h"
#include "Utilities.h"
#include "FreeImageTag.h"
#include "FIRational.h"

#define MAX_TEXT_EXTENT	512

/**
Convert a tag to a C string
*/
static const char*
ConvertAnyTag(FITAG *tag) {
	char format[MAX_TEXT_EXTENT];
	static std::string buffer;
	DWORD i;

	if(!tag)
		return NULL;

	buffer.erase();

	// convert the tag value to a string buffer

	FREE_IMAGE_MDTYPE tag_type = FreeImage_GetTagType(tag);
	DWORD tag_count = FreeImage_GetTagCount(tag);

	switch(tag_type) {
		case FIDT_BYTE:		// N x 8-bit unsigned integer
		{
			BYTE *pvalue = (BYTE*)FreeImage_GetTagValue(tag);

			sprintf(format, "%ld",	(LONG) pvalue[0]);
			buffer += format;
			for(i = 1; i < tag_count; i++) {
				sprintf(format, " %ld",	(LONG) pvalue[i]);
				buffer += format;
			}
			break;
		}
		case FIDT_SHORT:	// N x 16-bit unsigned integer
		{
			unsigned short *pvalue = (unsigned short *)FreeImage_GetTagValue(tag);

			sprintf(format, "%hu", pvalue[0]);
			buffer += format;
			for(i = 1; i < tag_count; i++) {
				sprintf(format, " %hu",	pvalue[i]);
				buffer += format;
			}
			break;
		}
		case FIDT_LONG:		// N x 32-bit unsigned integer
		{
			DWORD *pvalue = (DWORD *)FreeImage_GetTagValue(tag);

			sprintf(format, "%lu", pvalue[0]);
			buffer += format;
			for(i = 1; i < tag_count; i++) {
				sprintf(format, " %lu",	pvalue[i]);
				buffer += format;
			}
			break;
		}
		case FIDT_RATIONAL: // N x 64-bit unsigned fraction
		{
			DWORD *pvalue = (DWORD*)FreeImage_GetTagValue(tag);

			sprintf(format, "%ld/%ld", pvalue[0], pvalue[1]);
			buffer += format;
			for(i = 1; i < tag_count; i++) {
				sprintf(format, " %ld/%ld", pvalue[2*i], pvalue[2*i+1]);
				buffer += format;
			}
			break;
		}
		case FIDT_SBYTE:	// N x 8-bit signed integer
		{
			char *pvalue = (char*)FreeImage_GetTagValue(tag);

			sprintf(format, "%ld",	(LONG) pvalue[0]);
			buffer += format;
			for(i = 1; i < tag_count; i++) {
				sprintf(format, " %ld",	(LONG) pvalue[i]);
				buffer += format;
			}
			break;
		}
		case FIDT_SSHORT:	// N x 16-bit signed integer
		{
			short *pvalue = (short *)FreeImage_GetTagValue(tag);

			sprintf(format, "%hd", pvalue[0]);
			buffer += format;
			for(i = 1; i < tag_count; i++) {
				sprintf(format, " %hd",	pvalue[i]);
				buffer += format;
			}
			break;
		}
		case FIDT_SLONG:	// N x 32-bit signed integer
		{
			LONG *pvalue = (LONG *)FreeImage_GetTagValue(tag);

			sprintf(format, "%ld", pvalue[0]);
			buffer += format;
			for(i = 1; i < tag_count; i++) {
				sprintf(format, " %ld",	pvalue[i]);
				buffer += format;
			}
			break;
		}
		case FIDT_SRATIONAL:// N x 64-bit signed fraction
		{
			LONG *pvalue = (LONG*)FreeImage_GetTagValue(tag);

			sprintf(format, "%ld/%ld", pvalue[0], pvalue[1]);
			buffer += format;
			for(i = 1; i < tag_count; i++) {
				sprintf(format, " %ld/%ld", pvalue[2*i], pvalue[2*i+1]);
				buffer += format;
			}
			break;
		}
		case FIDT_FLOAT:	// N x 32-bit IEEE floating point
		{
			float *pvalue = (float *)FreeImage_GetTagValue(tag);

			sprintf(format, "%f", (double) pvalue[0]);
			buffer += format;
			for(i = 1; i < tag_count; i++) {
				sprintf(format, "%f", (double) pvalue[i]);
				buffer += format;
			}
			break;
		}
		case FIDT_DOUBLE:	// N x 64-bit IEEE floating point
		{
			double *pvalue = (double *)FreeImage_GetTagValue(tag);

			sprintf(format, "%f", pvalue[0]);
			buffer += format;
			for(i = 1; i < tag_count; i++) {
				sprintf(format, "%f", pvalue[i]);
				buffer += format;
			}
			break;
		}
		case FIDT_IFD:		// N x 32-bit unsigned integer (offset)
		{
			DWORD *pvalue = (DWORD *)FreeImage_GetTagValue(tag);

			sprintf(format, "%X", pvalue[0]);
			buffer += format;
			for(i = 1; i < tag_count; i++) {
				sprintf(format, " %X",	pvalue[i]);
				buffer += format;
			}
			break;
		}
		case FIDT_PALETTE:	// N x 32-bit RGBQUAD
		{
			RGBQUAD *pvalue = (RGBQUAD *)FreeImage_GetTagValue(tag);

			sprintf(format, "(%d,%d,%d,%d)", pvalue[0].rgbRed, pvalue[0].rgbGreen, pvalue[0].rgbBlue, pvalue[0].rgbReserved);
			buffer += format;
			for(i = 1; i < tag_count; i++) {
				sprintf(format, " (%d,%d,%d,%d)", pvalue[i].rgbRed, pvalue[i].rgbGreen, pvalue[i].rgbBlue, pvalue[i].rgbReserved);
				buffer += format;
			}
			break;
		}

		case FIDT_LONG8:	// N x 64-bit unsigned integer
		{
			UINT64 *pvalue = (UINT64 *)FreeImage_GetTagValue(tag);

			sprintf(format, "%ld", pvalue[0]);
			buffer += format;
			for(i = 1; i < tag_count; i++) {
				sprintf(format, "%ld", pvalue[i]);
				buffer += format;
			}
			break;
		}

		case FIDT_IFD8:		// N x 64-bit unsigned integer (offset)
		{
			UINT64 *pvalue = (UINT64 *)FreeImage_GetTagValue(tag);

			sprintf(format, "%X", pvalue[0]);
			buffer += format;
			for(i = 1; i < tag_count; i++) {
				sprintf(format, "%X", pvalue[i]);
				buffer += format;
			}
			break;
		}

		case FIDT_SLONG8:	// N x 64-bit signed integer
		{
			INT64 *pvalue = (INT64 *)FreeImage_GetTagValue(tag);

			sprintf(format, "%ld", pvalue[0]);
			buffer += format;
			for(i = 1; i < tag_count; i++) {
				sprintf(format, "%ld", pvalue[i]);
				buffer += format;
			}
			break;
		}

		case FIDT_ASCII:	// 8-bit bytes w/ last byte null
		case FIDT_UNDEFINED:// 8-bit untyped data
		default:
		{
			int max_size = MIN((int)FreeImage_GetTagLength(tag), (int)MAX_TEXT_EXTENT);
			if(max_size == MAX_TEXT_EXTENT)
				max_size--;
			memcpy(format, (char*)FreeImage_GetTagValue(tag), max_size);
			format[max_size] = '\0';
			buffer += format;
			break;
		}
	}

	return buffer.c_str();
}

/**
Convert a Exif tag to a C string
*/
static const char*
ConvertExifTag(FITAG *tag) {
	char format[MAX_TEXT_EXTENT];
	static std::string buffer;

	if(!tag)
		return NULL;

	buffer.erase();

	// convert the tag value to a string buffer

	switch(FreeImage_GetTagID(tag)) {
		case TAG_ORIENTATION:
		{
			unsigned short orientation = *((unsigned short *)FreeImage_GetTagValue(tag));
			switch (orientation) {
				case 1:
					return "top, left side";
				case 2:
					return "top, right side";
				case 3:
					return "bottom, right side";
				case 4:
					return "bottom, left side";
				case 5:
					return "left side, top";
				case 6:
					return "right side, top";
				case 7:
					return "right side, bottom";
				case 8:
					return "left side, bottom";
				default:
					break;
			}
		}
		break;

		case TAG_REFERENCE_BLACK_WHITE:
		{
			DWORD *pvalue = (DWORD*)FreeImage_GetTagValue(tag);
			if(FreeImage_GetTagLength(tag) == 48) {
				// reference black point value and reference white point value (ReferenceBlackWhite)
				int blackR = 0, whiteR = 0, blackG = 0, whiteG = 0, blackB = 0, whiteB = 0;
				if(pvalue[1])
					blackR = (int)(pvalue[0] / pvalue[1]);
				if(pvalue[3])
					whiteR = (int)(pvalue[2] / pvalue[3]);
				if(pvalue[5])
					blackG = (int)(pvalue[4] / pvalue[5]);
				if(pvalue[7])
					whiteG = (int)(pvalue[6] / pvalue[7]);
				if(pvalue[9])
					blackB = (int)(pvalue[8] / pvalue[9]);
				if(pvalue[11])
					whiteB = (int)(pvalue[10] / pvalue[11]);

				sprintf(format, "[%d,%d,%d] [%d,%d,%d]", blackR, blackG, blackB, whiteR, whiteG, whiteB);
				buffer += format;
				return buffer.c_str();
			}

		}
		break;

		case TAG_COLOR_SPACE:
		{
			unsigned short colorSpace = *((unsigned short *)FreeImage_GetTagValue(tag));
			if (colorSpace == 1) {
				return "sRGB";
			} else if (colorSpace == 65535) {
				return "Undefined";
			} else {
				return "Unknown";
			}
		}
		break;

		case TAG_COMPONENTS_CONFIGURATION:
		{
			const char *componentStrings[7] = {"", "Y", "Cb", "Cr", "R", "G", "B"};
			BYTE *pvalue = (BYTE*)FreeImage_GetTagValue(tag);
			for(DWORD i = 0; i < MIN((DWORD)4, FreeImage_GetTagCount(tag)); i++) {
				int j = pvalue[i];
				if(j > 0 && j < 7)
					buffer += componentStrings[j];
			}
			return buffer.c_str();
		}
		break;

		case TAG_COMPRESSED_BITS_PER_PIXEL:
		{
			FIRational r(tag);
			buffer = r.toString();
			if(buffer == "1")
				buffer += " bit/pixel";
			else
				buffer += " bits/pixel";
			return buffer.c_str();
		}
		break;

		case TAG_X_RESOLUTION:
		case TAG_Y_RESOLUTION:
		case TAG_FOCAL_PLANE_X_RES:
		case TAG_FOCAL_PLANE_Y_RES:
		case TAG_BRIGHTNESS_VALUE:
		case TAG_EXPOSURE_BIAS_VALUE:
		{
			FIRational r(tag);
			buffer = r.toString();
			return buffer.c_str();
		}
		break;

		case TAG_RESOLUTION_UNIT:
		case TAG_FOCAL_PLANE_UNIT:
		{
			unsigned short resolutionUnit = *((unsigned short *)FreeImage_GetTagValue(tag));
			switch (resolutionUnit) {
				case 1:
					return "(No unit)";
				case 2:
					return "inches";
				case 3:
					return "cm";
				default:
					break;
			}
		}
		break;

		case TAG_YCBCR_POSITIONING:
		{
			unsigned short yCbCrPosition = *((unsigned short *)FreeImage_GetTagValue(tag));
			switch (yCbCrPosition) {
				case 1:
					return "Center of pixel array";
				case 2:
					return "Datum point";
				default:
					break;
			}
		}
		break;

		case TAG_EXPOSURE_TIME:
		{
			FIRational r(tag);
			buffer = r.toString();
			buffer += " sec";
			return buffer.c_str();
		}
		break;

		case TAG_SHUTTER_SPEED_VALUE:
		{
			FIRational r(tag);
			LONG apexValue = r.longValue();
			LONG apexPower = 1 << apexValue;
			sprintf(format, "1/%d sec", (int)apexPower);
			buffer += format;
			return buffer.c_str();
		}
		break;

		case TAG_APERTURE_VALUE:
		case TAG_MAX_APERTURE_VALUE:
		{
			FIRational r(tag);
			double apertureApex = r.doubleValue();
	        double rootTwo = sqrt((double)2);
			double fStop = pow(rootTwo, apertureApex);
			sprintf(format, "F%.1f", fStop);
			buffer += format;
			return buffer.c_str();
		}
		break;

		case TAG_FNUMBER:
		{
			FIRational r(tag);
			double fnumber = r.doubleValue();
			sprintf(format, "F%.1f", fnumber);
			buffer += format;
			return buffer.c_str();
		}
		break;

		case TAG_FOCAL_LENGTH:
		{
			FIRational r(tag);
			double focalLength = r.doubleValue();
			sprintf(format, "%.1f mm", focalLength);
			buffer += format;
			return buffer.c_str();
		}
		break;

		case TAG_FOCAL_LENGTH_IN_35MM_FILM:
		{
			unsigned short focalLength = *((unsigned short *)FreeImage_GetTagValue(tag));
			sprintf(format, "%hu mm", focalLength);
			buffer += format;
			return buffer.c_str();
		}
		break;

		case TAG_FLASH:
		{
			unsigned short flash = *((unsigned short *)FreeImage_GetTagValue(tag));
			switch(flash) {
				case 0x0000:
					return "Flash did not fire";
				case 0x0001:
					return "Flash fired";
				case 0x0005:
					return "Strobe return light not detected";
				case 0x0007:
					return "Strobe return light detected";
				case 0x0009:
					return "Flash fired, compulsory flash mode";
				case 0x000D:
					return "Flash fired, compulsory flash mode, return light not detected";
				case 0x000F:
					return "Flash fired, compulsory flash mode, return light detected";
				case 0x0010:
					return "Flash did not fire, compulsory flash mode";
				case 0x0018:
					return "Flash did not fire, auto mode";
				case 0x0019:
					return "Flash fired, auto mode";
				case 0x001D:
					return "Flash fired, auto mode, return light not detected";
				case 0x001F:
					return "Flash fired, auto mode, return light detected";
				case 0x0020:
					return "No flash function";
				case 0x0041:
					return "Flash fired, red-eye reduction mode";
				case 0x0045:
					return "Flash fired, red-eye reduction mode, return light not detected";
				case 0x0047:
					return "Flash fired, red-eye reduction mode, return light detected";
				case 0x0049:
					return "Flash fired, compulsory flash mode, red-eye reduction mode";
				case 0x004D:
					return "Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected";
				case 0x004F:
					return "Flash fired, compulsory flash mode, red-eye reduction mode, return light detected";
				case 0x0059:
					return "Flash fired, auto mode, red-eye reduction mode";
				case 0x005D:
					return "Flash fired, auto mode, return light not detected, red-eye reduction mode";
				case 0x005F:
					return "Flash fired, auto mode, return light detected, red-eye reduction mode";
				default:
					sprintf(format, "Unknown (%d)", flash);
					buffer += format;
					return buffer.c_str();
			}
		}
		break;

		case TAG_SCENE_TYPE:
		{
			BYTE sceneType = *((BYTE*)FreeImage_GetTagValue(tag));
			if (sceneType == 1) {
				return "Directly photographed image";
			} else {
				sprintf(format, "Unknown (%d)", sceneType);
				buffer += format;
				return buffer.c_str();
			}
		}
		break;

		case TAG_SUBJECT_DISTANCE:
		{
			FIRational r(tag);
			if(r.getNumerator() == 0xFFFFFFFF) {
				return "Infinity";
			} else if(r.getNumerator() == 0) {
				return "Distance unknown";
			} else {
				double distance = r.doubleValue();
				sprintf(format, "%.3f meters", distance);
				buffer += format;
				return buffer.c_str();
			}
		}
		break;

		case TAG_METERING_MODE:
		{
			unsigned short meteringMode = *((unsigned short *)FreeImage_GetTagValue(tag));
			switch (meteringMode) {
				case 0:
					return "Unknown";
				case 1:
					return "Average";
				case 2:
					return "Center weighted average";
				case 3:
					return "Spot";
				case 4:
					return "Multi-spot";
				case 5:
					return "Multi-segment";
				case 6:
					return "Partial";
				case 255:
					return "(Other)";
				default:
					return "";
			}
		}
		break;

		case TAG_LIGHT_SOURCE:
		{
			unsigned short lightSource = *((unsigned short *)FreeImage_GetTagValue(tag));
			switch (lightSource) {
				case 0:
					return "Unknown";
				case 1:
					return "Daylight";
				case 2:
					return "Fluorescent";
				case 3:
					return "Tungsten (incandescent light)";
				case 4:
					return "Flash";
				case 9:
					return "Fine weather";
				case 10:
					return "Cloudy weather";
				case 11:
					return "Shade";
				case 12:
					return "Daylight fluorescent (D 5700 - 7100K)";
				case 13:
					return "Day white fluorescent (N 4600 - 5400K)";
				case 14:
					return "Cool white fluorescent (W 3900 - 4500K)";
				case 15:
					return "White fluorescent (WW 3200 - 3700K)";
				case 17:
					return "Standard light A";
				case 18:
					return "Standard light B";
				case 19:
					return "Standard light C";
				case 20:
					return "D55";
				case 21:
					return "D65";
				case 22:
					return "D75";
				case 23:
					return "D50";
				case 24:
					return "ISO studio tungsten";
				case 255:
					return "(Other)";
				default:
					return "";
			}
		}
		break;

		case TAG_SENSING_METHOD:
		{
			unsigned short sensingMethod = *((unsigned short *)FreeImage_GetTagValue(tag));

			switch (sensingMethod) {
				case 1:
					return "(Not defined)";
				case 2:
					return "One-chip color area sensor";
				case 3:
					return "Two-chip color area sensor";
				case 4:
					return "Three-chip color area sensor";
				case 5:
					return "Color sequential area sensor";
				case 7:
					return "Trilinear sensor";
				case 8:
					return "Color sequential linear sensor";
				default:
					return "";
			}
		}
		break;

		case TAG_FILE_SOURCE:
		{
			BYTE fileSource = *((BYTE*)FreeImage_GetTagValue(tag));
			if (fileSource == 3) {
				return "Digital Still Camera (DSC)";
			} else {
				sprintf(format, "Unknown (%d)", fileSource);
				buffer += format;
				return buffer.c_str();
			}
        }
		break;

		case TAG_EXPOSURE_PROGRAM:
		{
			unsigned short exposureProgram = *((unsigned short *)FreeImage_GetTagValue(tag));

			switch (exposureProgram) {
				case 1:
					return "Manual control";
				case 2:
					return "Program normal";
				case 3:
					return "Aperture priority";
				case 4:
					return "Shutter priority";
				case 5:
					return "Program creative (slow program)";
				case 6:
					return "Program action (high-speed program)";
				case 7:
					return "Portrait mode";
				case 8:
					return "Landscape mode";
				default:
					sprintf(format, "Unknown program (%d)", exposureProgram);
					buffer += format;
					return buffer.c_str();
			}
		}
		break;

		case TAG_CUSTOM_RENDERED:
		{
			unsigned short customRendered = *((unsigned short *)FreeImage_GetTagValue(tag));

			switch (customRendered) {
				case 0:
					return "Normal process";
				case 1:
					return "Custom process";
				default:
					sprintf(format, "Unknown rendering (%d)", customRendered);
					buffer += format;
					return buffer.c_str();
			}
		}
		break;

		case TAG_EXPOSURE_MODE:
		{
			unsigned short exposureMode = *((unsigned short *)FreeImage_GetTagValue(tag));

			switch (exposureMode) {
				case 0:
					return "Auto exposure";
				case 1:
					return "Manual exposure";
				case 2:
					return "Auto bracket";
				default:
					sprintf(format, "Unknown mode (%d)", exposureMode);
					buffer += format;
					return buffer.c_str();
			}
		}
		break;

		case TAG_WHITE_BALANCE:
		{
			unsigned short whiteBalance = *((unsigned short *)FreeImage_GetTagValue(tag));

			switch (whiteBalance) {
				case 0:
					return "Auto white balance";
				case 1:
					return "Manual white balance";
				default:
					sprintf(format, "Unknown (%d)", whiteBalance);
					buffer += format;
					return buffer.c_str();
			}
		}
		break;

		case TAG_SCENE_CAPTURE_TYPE:
		{
			unsigned short sceneType = *((unsigned short *)FreeImage_GetTagValue(tag));

			switch (sceneType) {
				case 0:
					return "Standard";
				case 1:
					return "Landscape";
				case 2:
					return "Portrait";
				case 3:
					return "Night scene";
				default:
					sprintf(format, "Unknown (%d)", sceneType);
					buffer += format;
					return buffer.c_str();
			}
		}
		break;

		case TAG_GAIN_CONTROL:
		{
			unsigned short gainControl = *((unsigned short *)FreeImage_GetTagValue(tag));

			switch (gainControl) {
				case 0:
					return "None";
				case 1:
					return "Low gain up";
				case 2:
					return "High gain up";
				case 3:
					return "Low gain down";
				case 4:
					return "High gain down";
				default:
					sprintf(format, "Unknown (%d)", gainControl);
					buffer += format;
					return buffer.c_str();
			}
		}
		break;

		case TAG_CONTRAST:
		{
			unsigned short contrast = *((unsigned short *)FreeImage_GetTagValue(tag));

			switch (contrast) {
				case 0:
					return "Normal";
				case 1:
					return "Soft";
				case 2:
					return "Hard";
				default:
					sprintf(format, "Unknown (%d)", contrast);
					buffer += format;
					return buffer.c_str();
			}
		}
		break;

		case TAG_SATURATION:
		{
			unsigned short saturation = *((unsigned short *)FreeImage_GetTagValue(tag));

			switch (saturation) {
				case 0:
					return "Normal";
				case 1:
					return "Low saturation";
				case 2:
					return "High saturation";
				default:
					sprintf(format, "Unknown (%d)", saturation);
					buffer += format;
					return buffer.c_str();
			}
		}
		break;

		case TAG_SHARPNESS:
		{
			unsigned short sharpness = *((unsigned short *)FreeImage_GetTagValue(tag));

			switch (sharpness) {
				case 0:
					return "Normal";
				case 1:
					return "Soft";
				case 2:
					return "Hard";
				default:
					sprintf(format, "Unknown (%d)", sharpness);
					buffer += format;
					return buffer.c_str();
			}
		}
		break;

		case TAG_SUBJECT_DISTANCE_RANGE:
		{
			unsigned short distanceRange = *((unsigned short *)FreeImage_GetTagValue(tag));

			switch (distanceRange) {
				case 0:
					return "unknown";
				case 1:
					return "Macro";
				case 2:
					return "Close view";
				case 3:
					return "Distant view";
				default:
					sprintf(format, "Unknown (%d)", distanceRange);
					buffer += format;
					return buffer.c_str();
			}
		}
		break;

		case TAG_ISO_SPEED_RATINGS:
		{
			unsigned short isoEquiv = *((unsigned short *)FreeImage_GetTagValue(tag));
			if (isoEquiv < 50) {
				isoEquiv *= 200;
			}
			sprintf(format, "%d", isoEquiv);
			buffer += format;
			return buffer.c_str();
		}
		break;

		case TAG_USER_COMMENT:
		{
			// first 8 bytes are used to define an ID code
			// we assume this is an ASCII string
			const BYTE *userComment = (BYTE*)FreeImage_GetTagValue(tag);
			for(DWORD i = 8; i < FreeImage_GetTagLength(tag); i++) {
				buffer += userComment[i];
			}
			buffer += '\0';
			return buffer.c_str();
		}
		break;

		case TAG_COMPRESSION:
		{
			WORD compression = *((WORD*)FreeImage_GetTagValue(tag));
			switch(compression) {
				case TAG_COMPRESSION_NONE:
					sprintf(format, "dump mode (%d)", compression);
					break;
				case TAG_COMPRESSION_CCITTRLE:
					sprintf(format, "CCITT modified Huffman RLE (%d)", compression);
					break;
				case TAG_COMPRESSION_CCITTFAX3:
					sprintf(format, "CCITT Group 3 fax encoding (%d)", compression);
					break;
				/*
				case TAG_COMPRESSION_CCITT_T4:
					sprintf(format, "CCITT T.4 (TIFF 6 name) (%d)", compression);
					break;
				*/
				case TAG_COMPRESSION_CCITTFAX4:
					sprintf(format, "CCITT Group 4 fax encoding (%d)", compression);
					break;
				/*
				case TAG_COMPRESSION_CCITT_T6:
					sprintf(format, "CCITT T.6 (TIFF 6 name) (%d)", compression);
					break;
				*/
				case TAG_COMPRESSION_LZW:
					sprintf(format, "LZW (%d)", compression);
					break;
				case TAG_COMPRESSION_OJPEG:
					sprintf(format, "!6.0 JPEG (%d)", compression);
					break;
				case TAG_COMPRESSION_JPEG:
					sprintf(format, "JPEG (%d)", compression);
					break;
				case TAG_COMPRESSION_NEXT:
					sprintf(format, "NeXT 2-bit RLE (%d)", compression);
					break;
				case TAG_COMPRESSION_CCITTRLEW:
					sprintf(format, "CCITTRLEW (%d)", compression);
					break;
				case TAG_COMPRESSION_PACKBITS:
					sprintf(format, "PackBits Macintosh RLE (%d)", compression);
					break;
				case TAG_COMPRESSION_THUNDERSCAN:
					sprintf(format, "ThunderScan RLE (%d)", compression);
					break;
				case TAG_COMPRESSION_PIXARFILM:
					sprintf(format, "Pixar companded 10bit LZW (%d)", compression);
					break;
				case TAG_COMPRESSION_PIXARLOG:
					sprintf(format, "Pixar companded 11bit ZIP (%d)", compression);
					break;
				case TAG_COMPRESSION_DEFLATE:
					sprintf(format, "Deflate compression (%d)", compression);
					break;
				case TAG_COMPRESSION_ADOBE_DEFLATE:
					sprintf(format, "Adobe Deflate compression (%d)", compression);
					break;
				case TAG_COMPRESSION_DCS:
					sprintf(format, "Kodak DCS encoding (%d)", compression);
					break;
				case TAG_COMPRESSION_JBIG:
					sprintf(format, "ISO JBIG (%d)", compression);
					break;
				case TAG_COMPRESSION_SGILOG:
					sprintf(format, "SGI Log Luminance RLE (%d)", compression);
					break;
				case TAG_COMPRESSION_SGILOG24:
					sprintf(format, "SGI Log 24-bit packed (%d)", compression);
					break;
				case TAG_COMPRESSION_JP2000:
					sprintf(format, "Leadtools JPEG2000 (%d)", compression);
					break;
				case TAG_COMPRESSION_LZMA:
					sprintf(format, "LZMA2 (%d)", compression);
					break;
				default:
					sprintf(format, "Unknown type (%d)", compression);
					break;
			}

			buffer += format;
			return buffer.c_str();
		}
		break;
	}

	return ConvertAnyTag(tag);
}

/**
Convert a Exif GPS tag to a C string
*/
static const char*
ConvertExifGPSTag(FITAG *tag) {
	char format[MAX_TEXT_EXTENT];
	static std::string buffer;

	if(!tag)
		return NULL;

	buffer.erase();

	// convert the tag value to a string buffer

	switch(FreeImage_GetTagID(tag)) {
		case TAG_GPS_LATITUDE:
		case TAG_GPS_LONGITUDE:
		case TAG_GPS_TIME_STAMP:
		{
			DWORD *pvalue = (DWORD*)FreeImage_GetTagValue(tag);
			if(FreeImage_GetTagLength(tag) == 24) {
				// dd:mm:ss or hh:mm:ss
				int dd = 0, mm = 0;
				double ss = 0;

				// convert to seconds
				if(pvalue[1])
					ss += ((double)pvalue[0] / (double)pvalue[1]) * 3600;
				if(pvalue[3])
					ss += ((double)pvalue[2] / (double)pvalue[3]) * 60;
				if(pvalue[5])
					ss += ((double)pvalue[4] / (double)pvalue[5]);

				// convert to dd:mm:ss.ss
				dd = (int)(ss / 3600);
				mm = (int)(ss / 60) - dd * 60;
				ss = ss - dd * 3600 - mm * 60;

				sprintf(format, "%d:%d:%.2f", dd, mm, ss);
				buffer += format;
				return buffer.c_str();
			}
		}
		break;

		case TAG_GPS_VERSION_ID:
		case TAG_GPS_LATITUDE_REF:
		case TAG_GPS_LONGITUDE_REF:
		case TAG_GPS_ALTITUDE_REF:
		case TAG_GPS_ALTITUDE:
		case TAG_GPS_SATELLITES:
		case TAG_GPS_STATUS:
		case TAG_GPS_MEASURE_MODE:
		case TAG_GPS_DOP:
		case TAG_GPS_SPEED_REF:
		case TAG_GPS_SPEED:
		case TAG_GPS_TRACK_REF:
		case TAG_GPS_TRACK:
		case TAG_GPS_IMG_DIRECTION_REF:
		case TAG_GPS_IMG_DIRECTION:
		case TAG_GPS_MAP_DATUM:
		case TAG_GPS_DEST_LATITUDE_REF:
		case TAG_GPS_DEST_LATITUDE:
		case TAG_GPS_DEST_LONGITUDE_REF:
		case TAG_GPS_DEST_LONGITUDE:
		case TAG_GPS_DEST_BEARING_REF:
		case TAG_GPS_DEST_BEARING:
		case TAG_GPS_DEST_DISTANCE_REF:
		case TAG_GPS_DEST_DISTANCE:
		case TAG_GPS_PROCESSING_METHOD:
		case TAG_GPS_AREA_INFORMATION:
		case TAG_GPS_DATE_STAMP:
		case TAG_GPS_DIFFERENTIAL:
			break;
	}

	return ConvertAnyTag(tag);
}

// Tag to string conversion function


switch (sceneType) {
				case 0:
					return "Standard";
				case 1:
					return "Landscape";
				case 2:
					return "Portrait";
				case 3:
					return "Night scene";
				default:
					sprintf(format, "Unknown (%d)", sceneType);
					buffer += format;
					return buffer.c_str();
			}

// Tag to string conversion functions
// Design and implementation by

#ifdef _MSC_VER
#pragma warning (disable : 4786) // identifier was truncated to 'number' characters
#endif

#include "FreeImage.h"
#include "Utilities.h"
#include "FreeImageTag.h"
#include "FIRational.h"

#define MAX_TEXT_EXTENT	512

/**
Convert a tag to a C string
*/
static const char*
ConvertAnyTag(FITAG *tag) {
	char format[MAX_TEXT_EXTENT];
	static std::string buffer;
	DWORD i;

	if(!tag)
		return NULL;

	buffer.erase();

	// convert the tag value to a string buffer

	FREE_IMAGE_MDTYPE tag_type = FreeImage_GetTagType(tag);
	DWORD tag_count = FreeImage_GetTagCount(tag);

	switch(tag_type) {
		case FIDT_BYTE:		// N x 8-bit unsigned integer
		{
			BYTE *pvalue = (BYTE*)FreeImage_GetTagValue(tag);

			sprintf(format, "%ld",	(LONG) pvalue[0]);
			buffer += format;
			for(i = 1; i < tag_count; i++) {
				sprintf(format, " %ld",	(LONG) pvalue[i]);
				buffer += format;
			}
			break;
		}
		case FIDT_SHORT:	// N x 16-bit unsigned integer
		{
			unsigned short *pvalue = (unsigned short *)FreeImage_GetTagValue(tag);

			sprintf(format, "%hu", pvalue[0]);
			buffer += format;
			for(i = 1; i < tag_count; i++) {
				sprintf(format, " %hu",	pvalue[i]);
				buffer += format;
			}
			break;
		}
		case FIDT_LONG:		// N x 32-bit unsigned integer
		{
			DWORD *pvalue = (DWORD *)FreeImage_GetTagValue(tag);

			sprintf(format, "%lu", pvalue[0]);
			buffer += format;
			for(i = 1; i < tag_count; i++) {
				sprintf(format, " %lu",	pvalue[i]);
				buffer += format;
			}
			break;
		}
		case FIDT_RATIONAL: // N x 64-bit unsigned fraction
		{
			DWORD *pvalue = (DWORD*)FreeImage_GetTagValue(tag);

			sprintf(format, "%ld/%ld", pvalue[0], pvalue[1]);
			buffer += format;
			for(i = 1; i < tag_count; i++) {
				sprintf(format, " %ld/%ld", pvalue[2*i], pvalue[2*i+1]);
				buffer += format;
			}
			break;
		}
		case FIDT_SBYTE:	// N x 8-bit signed integer
		{
			char *pvalue = (char*)FreeImage_GetTagValue(tag);

			sprintf(format, "%ld",	(LONG) pvalue[0]);
			buffer += format;
			for(i = 1; i < tag_count; i++) {
				sprintf(format, " %ld",	(LONG) pvalue[i]);
				buffer += format;
			}
			break;
		}
		case FIDT_SSHORT:	// N x 16-bit signed integer
		{
			short *pvalue = (short *)FreeImage_GetTagValue(tag);

			sprintf(format, "%hd", pvalue[0]);
			buffer += format;
			for(i = 1; i < tag_count; i++) {
				sprintf(format, " %hd",	pvalue[i]);
				buffer += format;
			}
			break;
		}
		case FIDT_SLONG:	// N x 32-bit signed integer
		{
			LONG *pvalue = (LONG *)FreeImage_GetTagValue(tag);

			sprintf(format, "%ld", pvalue[0]);
			buffer += format;
			for(i = 1; i < tag_count; i++) {
				sprintf(format, " %ld",	pvalue[i]);
				buffer += format;
			}
			break;
		}
		case FIDT_SRATIONAL:// N x 64-bit signed fraction
		{
			LONG *pvalue = (LONG*)FreeImage_GetTagValue(tag);

			sprintf(format, "%ld/%ld", pvalue[0], pvalue[1]);
			buffer += format;
			for(i = 1; i < tag_count; i++) {
				sprintf(format, " %ld/%ld", pvalue[2*i], pvalue[2*i+1]);
				buffer += format;
			}
			break;
		}
		case FIDT_FLOAT:	// N x 32-bit IEEE floating point
		{
			float *pvalue = (float *)FreeImage_GetTagValue(tag);

			sprintf(format, "%f", (double) pvalue[0]);
			buffer += format;
			for(i = 1; i < tag_count; i++) {
				sprintf(format, "%f", (double) pvalue[i]);
				buffer += format;
			}
			break;
		}
		case FIDT_DOUBLE:	// N x 64-bit IEEE floating point
		{
			double *pvalue = (double *)FreeImage_GetTagValue(tag);

			sprintf(format, "%f", pvalue[0]);
			buffer += format;
			for(i = 1; i < tag_count; i++) {
				sprintf(format, "%f", pvalue[i]);
				buffer += format;
			}
			break;
		}
		case FIDT_IFD:		// N x 32-bit unsigned integer (offset)
		{
			DWORD *pvalue = (DWORD *)FreeImage_GetTagValue(tag);

			sprintf(format, "%X", pvalue[0]);
			buffer += format;
			for(i = 1; i < tag_count; i++) {
				sprintf(format, " %X",	pvalue[i]);
				buffer += format;
			}
			break;
		}
		case FIDT_PALETTE:	// N x 32-bit RGBQUAD
		{
			RGBQUAD *pvalue = (RGBQUAD *)FreeImage_GetTagValue(tag);

			sprintf(format, "(%d,%d,%d,%d)", pvalue[0].rgbRed, pvalue[0].rgbGreen, pvalue[0].rgbBlue, pvalue[0].rgbReserved);
			buffer += format;
			for(i = 1; i < tag_count; i++) {
				sprintf(format, " (%d,%d,%d,%d)", pvalue[i].rgbRed, pvalue[i].rgbGreen, pvalue[i].rgbBlue, pvalue[i].rgbReserved);
				buffer += format;
			}
			break;
		}

		case FIDT_LONG8:	// N x 64-bit unsigned integer
		{
			UINT64 *pvalue = (UINT64 *)FreeImage_GetTagValue(tag);

			sprintf(format, "%ld", pvalue[0]);
			buffer += format;
			for(i = 1; i < tag_count; i++) {
				sprintf(format, "%ld", pvalue[i]);
				buffer += format;
			}
			break;
		}

		case FIDT_IFD8:		// N x 64-bit unsigned integer (offset)
		{
			UINT64 *pvalue = (UINT64 *)FreeImage_GetTagValue(tag);

			sprintf(format, "%X", pvalue[0]);
			buffer += format;
			for(i = 1; i < tag_count; i++) {
				sprintf(format, "%X", pvalue[i]);
				buffer += format;
			}
			break;
		}

		case FIDT_SLONG8:	// N x 64-bit signed integer
		{
			INT64 *pvalue = (INT64 *)FreeImage_GetTagValue(tag);

			sprintf(format, "%ld", pvalue[0]);
			buffer += format;
			for(i = 1; i < tag_count; i++) {
				sprintf(format, "%ld", pvalue[i]);
				buffer += format;
			}
			break;
		}

		case FIDT_ASCII:	// 8-bit bytes w/ last byte null
		case FIDT_UNDEFINED:// 8-bit untyped data
		default:
		{
			int max_size = MIN((int)FreeImage_GetTagLength(tag), (int)MAX_TEXT_EXTENT);
			if(max_size == MAX_TEXT_EXTENT)
				max_size--;
			memcpy(format, (char*)FreeImage_GetTagValue(tag), max_size);
			format[max_size] = '\0';
			buffer += format;
			break;
		}
	}

	return buffer.c_str();
}

/**
Convert a Exif tag to a C string
*/
static const char*
ConvertExifTag(FITAG *tag) {
	char format[MAX_TEXT_EXTENT];
	static std::string buffer;

	if(!tag)
		return NULL;

	buffer.erase();

	// convert the tag value to a string buffer

	switch(FreeImage_GetTagID(tag)) {
		case TAG_ORIENTATION:
		{
			unsigned short orientation = *((unsigned short *)FreeImage_GetTagValue(tag));
			switch (orientation) {
				case 1:
					return "top, left side";
				case 2:
					return "top, right side";
				case 3:
					return "bottom, right side";
				case 4:
					return "bottom, left side";
				case 5:
					return "left side, top";
				case 6:
					return "right side, top";
				case 7:
					return "right side, bottom";
				case 8:
					return "left side, bottom";
				default:
					break;
			}
		}
		break;

		case TAG_REFERENCE_BLACK_WHITE:
		{
			DWORD *pvalue = (DWORD*)FreeImage_GetTagValue(tag);
			if(FreeImage_GetTagLength(tag) == 48) {
				// reference black point value and reference white point value (ReferenceBlackWhite)
				int blackR = 0, whiteR = 0, blackG = 0, whiteG = 0, blackB = 0, whiteB = 0;
				if(pvalue[1])
					blackR = (int)(pvalue[0] / pvalue[1]);
				if(pvalue[3])
					whiteR = (int)(pvalue[2] / pvalue[3]);
				if(pvalue[5])
					blackG = (int)(pvalue[4] / pvalue[5]);
				if(pvalue[7])
					whiteG = (int)(pvalue[6] / pvalue[7]);
				if(pvalue[9])
					blackB = (int)(pvalue[8] / pvalue[9]);
				if(pvalue[11])
					whiteB = (int)(pvalue[10] / pvalue[11]);

				sprintf(format, "[%d,%d,%d] [%d,%d,%d]", blackR, blackG, blackB, whiteR, whiteG, whiteB);
				buffer += format;
				return buffer.c_str();
			}

		}
		break;

		case TAG_COLOR_SPACE:
		{
			unsigned short colorSpace = *((unsigned short *)FreeImage_GetTagValue(tag));
			if (colorSpace == 1) {
				return "sRGB";
			} else if (colorSpace == 65535) {
				return "Undefined";
			} else {
				return "Unknown";
			}
		}
		break;

		case TAG_COMPONENTS_CONFIGURATION:
		{
			const char *componentStrings[7] = {"", "Y", "Cb", "Cr", "R", "G", "B"};
			BYTE *pvalue = (BYTE*)FreeImage_GetTagValue(tag);
			for(DWORD i = 0; i < MIN((DWORD)4, FreeImage_GetTagCount(tag)); i++) {
				int j = pvalue[i];
				if(j > 0 && j < 7)
					buffer += componentStrings[j];
			}
			return buffer.c_str();
		}
		break;

		case TAG_COMPRESSED_BITS_PER_PIXEL:
		{
			FIRational r(tag);
			buffer = r.toString();
			if(buffer == "1")
				buffer += " bit/pixel";
			else
				buffer += " bits/pixel";
			return buffer.c_str();
		}
		break;

		case TAG_X_RESOLUTION:
		case TAG_Y_RESOLUTION:
		case TAG_FOCAL_PLANE_X_RES:
		case TAG_FOCAL_PLANE_Y_RES:
		case TAG_BRIGHTNESS_VALUE:
		case TAG_EXPOSURE_BIAS_VALUE:
		{
			FIRational r(tag);
			buffer = r.toString();
			return buffer.c_str();
		}
		break;

		case TAG_RESOLUTION_UNIT:
		case TAG_FOCAL_PLANE_UNIT:
		{
			unsigned short resolutionUnit = *((unsigned short *)FreeImage_GetTagValue(tag));
			switch (resolutionUnit) {
				case 1:
					return "(No unit)";
				case 2:
					return "inches";
				case 3:
					return "cm";
				default:
					break;
			}
		}
		break;

		case TAG_YCBCR_POSITIONING:
		{
			unsigned short yCbCrPosition = *((unsigned short *)FreeImage_GetTagValue(tag));
			switch (yCbCrPosition) {
				case 1:
					return "Center of pixel array";
				case 2:
					return "Datum point";
				default:
					break;
			}
		}
		break;

		case TAG_EXPOSURE_TIME:
		{
			FIRational r(tag);
			buffer = r.toString();
			buffer += " sec";
			return buffer.c_str();
		}
		break;

		case TAG_SHUTTER_SPEED_VALUE:
		{
			FIRational r(tag);
			LONG apexValue = r.longValue();
			LONG apexPower = 1 << apexValue;
			sprintf(format, "1/%d sec", (int)apexPower);
			buffer += format;
			return buffer.c_str();
		}
		break;

		case TAG_APERTURE_VALUE:
		case TAG_MAX_APERTURE_VALUE:
		{
			FIRational r(tag);
			double apertureApex = r.doubleValue();
	        double rootTwo = sqrt((double)2);
			double fStop = pow(rootTwo, apertureApex);
			sprintf(format, "F%.1f", fStop);
			buffer += format;
			return buffer.c_str();
		}
		break;

		case TAG_FNUMBER:
		{
			FIRational r(tag);
			double fnumber = r.doubleValue();
			sprintf(format, "F%.1f", fnumber);
			buffer += format;
			return buffer.c_str();
		}
		break;

		case TAG_FOCAL_LENGTH:
		{
			FIRational r(tag);
			double focalLength = r.doubleValue();
			sprintf(format, "%.1f mm", focalLength);
			buffer += format;
			return buffer.c_str();
		}
		break;

		case TAG_FOCAL_LENGTH_IN_35MM_FILM:
		{
			unsigned short focalLength = *((unsigned short *)FreeImage_GetTagValue(tag));
			sprintf(format, "%hu mm", focalLength);
			buffer += format;
			return buffer.c_str();
		}
		break;

		case TAG_FLASH:
		{
			unsigned short flash = *((unsigned short *)FreeImage_GetTagValue(tag));
			switch(flash) {
				case 0x0000:
					return "Flash did not fire";
				case 0x0001:
					return "Flash fired";
				case 0x0005:
					return "Strobe return light not detected";
				case 0x0007:
					return "Strobe return light detected";
				case 0x0009:
					return "Flash fired, compulsory flash mode";
				case 0x000D:
					return "Flash fired, compulsory flash mode, return light not detected";
				case 0x000F:
					return "Flash fired, compulsory flash mode, return light detected";
				case 0x0010:
					return "Flash did not fire, compulsory flash mode";
				case 0x0018:
					return "Flash did not fire, auto mode";
				case 0x0019:
					return "Flash fired, auto mode";
				case 0x001D:
					return "Flash fired, auto mode, return light not detected";
				case 0x001F:
					return "Flash fired, auto mode, return light detected";
				case 0x0020:
					return "No flash function";
				case 0x0041:
					return "Flash fired, red-eye reduction mode";
				case 0x0045:
					return "Flash fired, red-eye reduction mode, return light not detected";
				case 0x0047:
					return "Flash fired, red-eye reduction mode, return light detected";
				case 0x0049:
					return "Flash fired, compulsory flash mode, red-eye reduction mode";
				case 0x004D:
					return "Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected";
				case 0x004F:
					return "Flash fired, compulsory flash mode, red-eye reduction mode, return light detected";
				case 0x0059:
					return "Flash fired, auto mode, red-eye reduction mode";
				case 0x005D:
					return "Flash fired, auto mode, return light not detected, red-eye reduction mode";
				case 0x005F:
					return "Flash fired, auto mode, return light detected, red-eye reduction mode";
				default:
					sprintf(format, "Unknown (%d)", flash);
					buffer += format;
					return buffer.c_str();
			}
		}
		break;

		case TAG_SCENE_TYPE:
		{
			BYTE sceneType = *((BYTE*)FreeImage_GetTagValue(tag));
			if (sceneType == 1) {
				return "Directly photographed image";
			} else {
				sprintf(format, "Unknown (%d)", sceneType);
				buffer += format;
				return buffer.c_str();
			}
		}
		break;

		case TAG_SUBJECT_DISTANCE:
		{
			FIRational r(tag);
			if(r.getNumerator() == 0xFFFFFFFF) {
				return "Infinity";
			} else if(r.getNumerator() == 0) {
				return "Distance unknown";
			} else {
				double distance = r.doubleValue();
				sprintf(format, "%.3f meters", distance);
				buffer += format;
				return buffer.c_str();
			}
		}
		break;

		case TAG_METERING_MODE:
		{
			unsigned short meteringMode = *((unsigned short *)FreeImage_GetTagValue(tag));
			switch (meteringMode) {
				case 0:
					return "Unknown";
				case 1:
					return "Average";
				case 2:
					return "Center weighted average";
				case 3:
					return "Spot";
				case 4:
					return "Multi-spot";
				case 5:
					return "Multi-segment";
				case 6:
					return "Partial";
				case 255:
					return "(Other)";
				default:
					return "";
			}
		}
		break;

		case TAG_LIGHT_SOURCE:
		{
			unsigned short lightSource = *((unsigned short *)FreeImage_GetTagValue(tag));
			switch (lightSource) {
				case 0:
					return "Unknown";
				case 1:
					return "Daylight";
				case 2:
					return "Fluorescent";
				case 3:
					return "Tungsten (incandescent light)";
				case 4:
					return "Flash";
				case 9:
					return "Fine weather";
				case 10:
					return "Cloudy weather";
				case 11:
					return "Shade";
				case 12:
					return "Daylight fluorescent (D 5700 - 7100K)";
				case 13:
					return "Day white fluorescent (N 4600 - 5400K)";
				case 14:
					return "Cool white fluorescent (W 3900 - 4500K)";
				case 15:
					return "White fluorescent (WW 3200 - 3700K)";
				case 17:
					return "Standard light A";
				case 18:
					return "Standard light B";
				case 19:
					return "Standard light C";
				case 20:
					return "D55";
				case 21:
					return "D65";
				case 22:
					return "D75";
				case 23:
					return "D50";
				case 24:
					return "ISO studio tungsten";
				case 255:
					return "(Other)";
				default:
					return "";
			}
		}
		break;

		case TAG_SENSING_METHOD:
		{
			unsigned short sensingMethod = *((unsigned short *)FreeImage_GetTagValue(tag));

			switch (sensingMethod) {
				case 1:
					return "(Not defined)";
				case 2:
					return "One-chip color area sensor";
				case 3:
					return "Two-chip color area sensor";
				case 4:
					return "Three-chip color area sensor";
				case 5:
					return "Color sequential area sensor";
				case 7:
					return "Trilinear sensor";
				case 8:
					return "Color sequential linear sensor";
				default:
					return "";
			}
		}
		break;

		case TAG_FILE_SOURCE:
		{
			BYTE fileSource = *((BYTE*)FreeImage_GetTagValue(tag));
			if (fileSource == 3) {
				return "Digital Still Camera (DSC)";
			} else {
				sprintf(format, "Unknown (%d)", fileSource);
				buffer += format;
				return buffer.c_str();
			}
        }
		break;

		case TAG_EXPOSURE_PROGRAM:
		{
			unsigned short exposureProgram = *((unsigned short *)FreeImage_GetTagValue(tag));

			switch (exposureProgram) {
				case 1:
					return "Manual control";
				case 2:
					return "Program normal";
				case 3:
					return "Aperture priority";
				case 4:
					return "Shutter priority";
				case 5:
					return "Program creative (slow program)";
				case 6:
					return "Program action (high-speed program)";
				case 7:
					return "Portrait mode";
				case 8:
					return "Landscape mode";
				default:
					sprintf(format, "Unknown program (%d)", exposureProgram);
					buffer += format;
					return buffer.c_str();
			}
		}
		break;

		case TAG_CUSTOM_RENDERED:
		{
			unsigned short customRendered = *((unsigned short *)FreeImage_GetTagValue(tag));

			switch (customRendered) {
				case 0:
					return "Normal process";
				case 1:
					return "Custom process";
				default:
					sprintf(format, "Unknown rendering (%d)", customRendered);
					buffer += format;
					return buffer.c_str();
			}
		}
		break;

		case TAG_EXPOSURE_MODE:
		{
			unsigned short exposureMode = *((unsigned short *)FreeImage_GetTagValue(tag));

			switch (exposureMode) {
				case 0:
					return "Auto exposure";
				case 1:
					return "Manual exposure";
				case 2:
					return "Auto bracket";
				default:
					sprintf(format, "Unknown mode (%d)", exposureMode);
					buffer += format;
					return buffer.c_str();
			}
		}
		break;

		case TAG_WHITE_BALANCE:
		{
			unsigned short whiteBalance = *((unsigned short *)FreeImage_GetTagValue(tag));

			switch (whiteBalance) {
				case 0:
					return "Auto white balance";
				case 1:
					return "Manual white balance";
				default:
					sprintf(format, "Unknown (%d)", whiteBalance);
					buffer += format;
					return buffer.c_str();
			}
		}
		break;

		case TAG_SCENE_CAPTURE_TYPE:
		{
			unsigned short sceneType = *((unsigned short *)FreeImage_GetTagValue(tag));

		}
		break;

		case TAG_GAIN_CONTROL:
		{
			unsigned short gainControl = *((unsigned short *)FreeImage_GetTagValue(tag));

			switch (gainControl) {
				case 0:
					return "None";
				case 1:
					return "Low gain up";
				case 2:
					return "High gain up";
				case 3:
					return "Low gain down";
				case 4:
					return "High gain down";
				default:
					sprintf(format, "Unknown (%d)", gainControl);
					buffer += format;
					return buffer.c_str();
			}
		}
		break;

		case TAG_CONTRAST:
		{
			unsigned short contrast = *((unsigned short *)FreeImage_GetTagValue(tag));

			switch (contrast) {
				case 0:
					return "Normal";
				case 1:
					return "Soft";
				case 2:
					return "Hard";
				default:
					sprintf(format, "Unknown (%d)", contrast);
					buffer += format;
					return buffer.c_str();
			}
		}
		break;

		case TAG_SATURATION:
		{
			unsigned short saturation = *((unsigned short *)FreeImage_GetTagValue(tag));

			switch (saturation) {
				case 0:
					return "Normal";
				case 1:
					return "Low saturation";
				case 2:
					return "High saturation";
				default:
					sprintf(format, "Unknown (%d)", saturation);
					buffer += format;
					return buffer.c_str();
			}
		}
		break;

		case TAG_SHARPNESS:
		{
			unsigned short sharpness = *((unsigned short *)FreeImage_GetTagValue(tag));

			switch (sharpness) {
				case 0:
					return "Normal";
				case 1:
					return "Soft";
				case 2:
					return "Hard";
				default:
					sprintf(format, "Unknown (%d)", sharpness);
					buffer += format;
					return buffer.c_str();
			}
		}
		break;

		case TAG_SUBJECT_DISTANCE_RANGE:
		{
			unsigned short distanceRange = *((unsigned short *)FreeImage_GetTagValue(tag));

			switch (distanceRange) {
				case 0:
					return "unknown";
				case 1:
					return "Macro";
				case 2:
					return "Close view";
				case 3:
					return "Distant view";
				default:
					sprintf(format, "Unknown (%d)", distanceRange);
					buffer += format;
					return buffer.c_str();
			}
		}
		break;

		case TAG_ISO_SPEED_RATINGS:
		{
			unsigned short isoEquiv = *((unsigned short *)FreeImage_GetTagValue(tag));
			if (isoEquiv < 50) {
				isoEquiv *= 200;
			}
			sprintf(format, "%d", isoEquiv);
			buffer += format;
			return buffer.c_str();
		}
		break;

		case TAG_USER_COMMENT:
		{
			// first 8 bytes are used to define an ID code
			// we assume this is an ASCII string
			const BYTE *userComment = (BYTE*)FreeImage_GetTagValue(tag);
			for(DWORD i = 8; i < FreeImage_GetTagLength(tag); i++) {
				buffer += userComment[i];
			}
			buffer += '\0';
			return buffer.c_str();
		}
		break;

		case TAG_COMPRESSION:
		{
			WORD compression = *((WORD*)FreeImage_GetTagValue(tag));
			switch(compression) {
				case TAG_COMPRESSION_NONE:
					sprintf(format, "dump mode (%d)", compression);
					break;
				case TAG_COMPRESSION_CCITTRLE:
					sprintf(format, "CCITT modified Huffman RLE (%d)", compression);
					break;
				case TAG_COMPRESSION_CCITTFAX3:
					sprintf(format, "CCITT Group 3 fax encoding (%d)", compression);
					break;
				/*
				case TAG_COMPRESSION_CCITT_T4:
					sprintf(format, "CCITT T.4 (TIFF 6 name) (%d)", compression);
					break;
				*/
				case TAG_COMPRESSION_CCITTFAX4:
					sprintf(format, "CCITT Group 4 fax encoding (%d)", compression);
					break;
				/*
				case TAG_COMPRESSION_CCITT_T6:
					sprintf(format, "CCITT T.6 (TIFF 6 name) (%d)", compression);
					break;
				*/
				case TAG_COMPRESSION_LZW:
					sprintf(format, "LZW (%d)", compression);
					break;
				case TAG_COMPRESSION_OJPEG:
					sprintf(format, "!6.0 JPEG (%d)", compression);
					break;
				case TAG_COMPRESSION_JPEG:
					sprintf(format, "JPEG (%d)", compression);
					break;
				case TAG_COMPRESSION_NEXT:
					sprintf(format, "NeXT 2-bit RLE (%d)", compression);
					break;
				case TAG_COMPRESSION_CCITTRLEW:
					sprintf(format, "CCITTRLEW (%d)", compression);
					break;
				case TAG_COMPRESSION_PACKBITS:
					sprintf(format, "PackBits Macintosh RLE (%d)", compression);
					break;
				case TAG_COMPRESSION_THUNDERSCAN:
					sprintf(format, "ThunderScan RLE (%d)", compression);
					break;
				case TAG_COMPRESSION_PIXARFILM:
					sprintf(format, "Pixar companded 10bit LZW (%d)", compression);
					break;
				case TAG_COMPRESSION_PIXARLOG:
					sprintf(format, "Pixar companded 11bit ZIP (%d)", compression);
					break;
				case TAG_COMPRESSION_DEFLATE:
					sprintf(format, "Deflate compression (%d)", compression);
					break;
				case TAG_COMPRESSION_ADOBE_DEFLATE:
					sprintf(format, "Adobe Deflate compression (%d)", compression);
					break;
				case TAG_COMPRESSION_DCS:
					sprintf(format, "Kodak DCS encoding (%d)", compression);
					break;
				case TAG_COMPRESSION_JBIG:
					sprintf(format, "ISO JBIG (%d)", compression);
					break;
				case TAG_COMPRESSION_SGILOG:
					sprintf(format, "SGI Log Luminance RLE (%d)", compression);
					break;
				case TAG_COMPRESSION_SGILOG24:
					sprintf(format, "SGI Log 24-bit packed (%d)", compression);
					break;
				case TAG_COMPRESSION_JP2000:
					sprintf(format, "Leadtools JPEG2000 (%d)", compression);
					break;
				case TAG_COMPRESSION_LZMA:
					sprintf(format, "LZMA2 (%d)", compression);
					break;
				default:
					sprintf(format, "Unknown type (%d)", compression);
					break;
			}

			buffer += format;
			return buffer.c_str();
		}
		break;
	}

	return ConvertAnyTag(tag);
}

/**
Convert a Exif GPS tag to a C string
*/
static const char*
ConvertExifGPSTag(FITAG *tag) {
	char format[MAX_TEXT_EXTENT];
	static std::string buffer;

	if(!tag)
		return NULL;

	buffer.erase();

	// convert the tag value to a string buffer

	switch(FreeImage_GetTagID(tag)) {
		case TAG_GPS_LATITUDE:
		case TAG_GPS_LONGITUDE:
		case TAG_GPS_TIME_STAMP:
		{
			DWORD *pvalue = (DWORD*)FreeImage_GetTagValue(tag);
			if(FreeImage_GetTagLength(tag) == 24) {
				// dd:mm:ss or hh:mm:ss
				int dd = 0, mm = 0;
				double ss = 0;

				// convert to seconds
				if(pvalue[1])
					ss += ((double)pvalue[0] / (double)pvalue[1]) * 3600;
				if(pvalue[3])
					ss += ((double)pvalue[2] / (double)pvalue[3]) * 60;
				if(pvalue[5])
					ss += ((double)pvalue[4] / (double)pvalue[5]);

				// convert to dd:mm:ss.ss
				dd = (int)(ss / 3600);
				mm = (int)(ss / 60) - dd * 60;
				ss = ss - dd * 3600 - mm * 60;

				sprintf(format, "%d:%d:%.2f", dd, mm, ss);
				buffer += format;
				return buffer.c_str();
			}
		}
		break;

		case TAG_GPS_VERSION_ID:
		case TAG_GPS_LATITUDE_REF:
		case TAG_GPS_LONGITUDE_REF:
		case TAG_GPS_ALTITUDE_REF:
		case TAG_GPS_ALTITUDE:
		case TAG_GPS_SATELLITES:
		case TAG_GPS_STATUS:
		case TAG_GPS_MEASURE_MODE:
		case TAG_GPS_DOP:
		case TAG_GPS_SPEED_REF:
		case TAG_GPS_SPEED:
		case TAG_GPS_TRACK_REF:
		case TAG_GPS_TRACK:
		case TAG_GPS_IMG_DIRECTION_REF:
		case TAG_GPS_IMG_DIRECTION:
		case TAG_GPS_MAP_DATUM:
		case TAG_GPS_DEST_LATITUDE_REF:
		case TAG_GPS_DEST_LATITUDE:
		case TAG_GPS_DEST_LONGITUDE_REF:
		case TAG_GPS_DEST_LONGITUDE:
		case TAG_GPS_DEST_BEARING_REF:
		case TAG_GPS_DEST_BEARING:
		case TAG_GPS_DEST_DISTANCE_REF:
		case TAG_GPS_DEST_DISTANCE:
		case TAG_GPS_PROCESSING_METHOD:
		case TAG_GPS_AREA_INFORMATION:
		case TAG_GPS_DATE_STAMP:
		case TAG_GPS_DIFFERENTIAL:
			break;
	}

	return ConvertAnyTag(tag);
}

// Tag to string conversion function

const char* DLL_CALLCONV
FreeImage_TagToString(FREE_IMAGE_MDMODEL model, FITAG *tag, char *Make) {
	switch(model) {
		case FIMD_EXIF_MAIN:
		case FIMD_EXIF_EXIF:
			return ConvertExifTag(tag);

		case FIMD_EXIF_GPS:
			return ConvertExifGPSTag(tag);

		case FIMD_EXIF_MAKERNOTE:
			// We should use the Make string to select an appropriate conversion function
			break;

		case FIMD_EXIF_INTEROP:
		default:
			break;
	}

	return ConvertAnyTag(tag);
}


void bind_const_lvalue_to_class_conv_temporary() {
  const Base &br1 = ConvertibleToBase();
  const Base &br2 = ConvertibleToDerived();
}

struct Base { };
struct Derived : Base { }; // expected-note{{candidate constructor (the implicit copy constructor) not viable}}
struct Unrelated { };
struct Derived2 : Base { };
struct Diamond : Derived, Derived2 { };

struct ConvertibleToBaseRef {
  operator Base&() const;
};

struct ConvertibleToDerivedRef {
  operator Derived&() const;
};

struct ConvertibleToBothDerivedRef {
  operator Derived&(); // expected-note{{candidate function}}
  operator Derived2&(); // expected-note{{candidate function}}
};

struct ConvertibleToIntRef {
  operator int&();
};

struct ConvertibleToBase {
  operator Base() const;
};

struct ConvertibleToDerived {
  operator Derived() const;
};

struct ConvertibleToBothDerived {
  operator Derived(); // expected-note{{candidate function}}
  operator Derived2(); // expected-note{{candidate function}}
};

struct ConvertibleToInt {
  operator int();
};

template<typename T> T create();

// First bullet: lvalue references binding to lvalues (the simple cases).
void bind_lvalue_to_lvalue(Base b, Derived d,
                           const Base bc, const Derived dc,
                           Diamond diamond,
                           int i) {
  // Reference-compatible
  Base &br1 = b;
  Base &br2 = d;
  Derived &dr1 = d;
  Derived &dr2 = b; // expected-error{{non-const lvalue reference to type 'Derived' cannot bind to a value of unrelated type 'Base'}}
  Base &br3 = bc; // expected-error{{drops 'const' qualifier}}
  Base &br4 = dc; // expected-error{{drops 'const' qualifier}}
  Base &br5 = diamond; // expected-error{{ambiguous conversion from derived class 'Diamond' to base class 'Base':}}
  int &ir = i;
  long &lr = i; // expected-error{{non-const lvalue reference to type 'long' cannot bind to a value of unrelated type 'int'}}
}

void bind_lvalue_quals(volatile Base b, volatile Derived d,
                       volatile const Base bvc, volatile const Derived dvc,
                       volatile const int ivc) {
  volatile Base &bvr1 = b;
  volatile Base &bvr2 = d;
  volatile Base &bvr3 = bvc; // expected-error{{binding value of type 'const volatile Base' to reference to type 'volatile Base' drops 'const' qualifier}}
  volatile Base &bvr4 = dvc; // expected-error{{binding value of type 'const volatile Derived' to reference to type 'volatile Base' drops 'const' qualifier}}

  volatile int &ir = ivc; // expected-error{{binding value of type 'const volatile int' to reference to type 'volatile int' drops 'const' qualifier}}

  const volatile Base &bcvr1 = b;
  const volatile Base &bcvr2 = d;
}

void bind_lvalue_to_rvalue() {
  Base &br1 = Base(); // expected-error{{non-const lvalue reference to type 'Base' cannot bind to a temporary of type 'Base'}}
  Base &br2 = Derived(); // expected-error{{non-const lvalue reference to type 'Base' cannot bind to a temporary of type 'Derived'}}
  const volatile Base &br3 = Base(); // expected-error{{volatile lvalue reference to type 'const volatile Base' cannot bind to a temporary of type 'Base'}}
  const volatile Base &br4 = Derived(); // expected-error{{volatile lvalue reference to type 'const volatile Base' cannot bind to a temporary of type 'Derived'}}

  int &ir = 17; // expected-error{{non-const lvalue reference to type 'int' cannot bind to a temporary of type 'int'}}
}

void bind_lvalue_to_unrelated(Unrelated ur) {
  Base &br1 = ur; // expected-error{{non-const lvalue reference to type 'Base' cannot bind to a value of unrelated type 'Unrelated'}}
  const volatile Base &br2 = ur; // expected-error{{volatile lvalue reference to type 'const volatile Base' cannot bind to a value of unrelated type 'Unrelated'}}
}

void bind_lvalue_to_conv_lvalue() {
  // Not reference-related, but convertible
  Base &nbr1 = ConvertibleToBaseRef();
  Base &nbr2 = ConvertibleToDerivedRef();
  Derived &ndr1 = ConvertibleToDerivedRef();
  int &ir = ConvertibleToIntRef();
}

void bind_lvalue_to_conv_lvalue_ambig(ConvertibleToBothDerivedRef both) {
  Derived &dr1 = both;
  Base &br1 = both; // expected-error{{reference initialization of type 'Base &' with initializer of type 'ConvertibleToBothDerivedRef' is ambiguous}}
}

struct IntBitfield {
  int i : 17; // expected-note{{bit-field is declared here}}
};

void test_bitfield(IntBitfield ib) {
  int & ir1 = (ib.i); // expected-error{{non-const reference cannot bind to bit-field 'i'}}
}

// Second bullet: const lvalue reference binding to an rvalue with
// similar type (both of which are class types).
void bind_const_lvalue_to_rvalue() {
  const Base &br1 = create<Base>();
  const Base &br2 = create<Derived>();
  const Derived &dr1 = create<Base>(); // expected-error{{no viable conversion}}

  const Base &br3 = create<const Base>();
  const Base &br4 = create<const Derived>();

  const Base &br5 = create<const volatile Base>(); // expected-error{{binding value of type 'const volatile Base' to reference to type 'const Base' drops 'volatile' qualifier}}
  const Base &br6 = create<const volatile Derived>(); // expected-error{{binding value of type 'const volatile Derived' to reference to type 'const Base' drops 'volatile' qualifier}}

  const int &ir = create<int>();
}

// Second bullet: const lvalue reference binds to the result of a conversion.

void bind_lvalue_to_conv_rvalue_ambig(ConvertibleToBothDerived both) {
  const Derived &dr1 = both;
  const Base &br1 = both; // expected-error{{reference initialization of type 'const Base &' with initializer of type 'ConvertibleToBothDerived' is ambiguous}}
}


bool
mutate_gnutls_record_send(ChanneledMessage& msg)
{
    static int version_cnt = 0;

    SIPFmt sip(msg.data);

    if (not sip.isValid()) {
        return false;
    }

    char version[]     = "SIP/2.0";
    char version_fmt[] = "SIP/%d.0";

    snprintf(version, array_size(version),
             version_fmt, (version_cnt++ % 2) + 1);

    sip.setVersion(version);

    /* Commit changes! */
    sip.swap(msg.data);

    return true;
}

/*
 *
 *  Author: Olivier Dion <olivier.dion>@savoirfairelinux.com>
 *
 */

#include <cstdlib>

#include "lib/gnutls.h"
#include "lib/sip-fmt.h"

/*
 * Example fuzzing wrapper that will change the SIP version sent randomly
 * between 1.0 and 2.0
 */