/*
 *  This file is part of RawTherapee.
 *
 *  Copyright (c) 2004-2010 Gabor Horvath <hgabor@rawtherapee.com>
 *  Some parts of the source code (e.g. ciff support) are taken from dcraw
 *  that is copyrighted by Dave Coffin
 *
 *  RawTherapee is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 * 
 *  RawTherapee is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with RawTherapee.  If not, see <http://www.gnu.org/licenses/>.
 */
#include <cstdio>
#include <cmath>
#include <cstring>
#include <ctime>
#include <sstream>
#include <stdint.h>

#include "../rtgui/cacheimagedata.h"
#include "rtexif.h"
#include "../rtengine/safegtk.h"
#include "../rtgui/version.h"
#include "../rtgui/ppversion.h"

using namespace std;

namespace rtexif {

Interpreter stdInterpreter;

//--------------- class TagDirectory ------------------------------------------
// this class is a collection (an array) of tags
//-----------------------------------------------------------------------------

#define TAG_SUBFILETYPE 0x00fe

TagDirectory::TagDirectory () 
  : attribs(ifdAttribs), order(INTEL), parent(NULL) {}

TagDirectory::TagDirectory (TagDirectory* p, const TagAttrib* ta, ByteOrder border) 
    : attribs(ta), order(border), parent(p) {}

TagDirectory::TagDirectory (TagDirectory* p, FILE* f, int base, const TagAttrib* ta, ByteOrder border, bool skipIgnored)
  : attribs(ta), order(border), parent(p)
{

  int numOfTags = get2 (f, order);
  if (numOfTags<=0 || numOfTags>1000) // KodakIfd has lots of tags, thus 1000 as the limit
    return;
    
  bool thumbdescr = false;
  for (int i=0; i<numOfTags; i++) {

    Tag* newTag = new Tag (this, f, base);

    // filter out tags with unknown type
    if ((int)newTag->getType()==0) {
        delete newTag;
        continue;
    }
        
    if (skipIgnored) {
    int id = newTag->getID();

    // detect and possibly ignore tags of directories belonging to the embedded thumbnail image
    if (attribs==ifdAttribs && id==TAG_SUBFILETYPE && newTag->toInt()!=0)
        thumbdescr = true;

    const TagAttrib* attrib = getAttrib (id);

    if (!attrib || attrib->ignore==1 || (thumbdescr && attrib->ignore==2)) 
      delete newTag;
    else 
        addTag (newTag);
    } else addTag (newTag);
  }  
}
 
TagDirectory::~TagDirectory () {

  for (size_t i=0; i<tags.size(); i++)
    delete tags[i];
}

class CompareTags {
public:
    int operator() (Tag* const& a, Tag* const& b) const {
        return a->getID() < b->getID();
    }
};

void TagDirectory::sort () {

    std::sort (tags.begin(), tags.end(), CompareTags());
    for (size_t i=0; i<tags.size(); i++)
        if (tags[i]->isDirectory())
            for (int j=0; tags[i]->getDirectory(j); j++)
                tags[i]->getDirectory(j)->sort ();
}
TagDirectory*  TagDirectory::getRoot()
{
    if(parent) return parent->getRoot();
    else return this;
}

const TagAttrib* TagDirectory::getAttrib (int id) {

    if (attribs)
        for (int i=0; attribs[i].ignore!=-1; i++)
            if (attribs[i].ID==id)
                return &attribs[i];

    return NULL;
}

const TagAttrib* TagDirectory::getAttrib (const char* name) {

    if (attribs)
        for (int i=0; attribs[i].ignore!=-1; i++)
            if (!strcmp (attribs[i].name, name))
                return &attribs[i];

    return NULL;
}

const TagAttrib* TagDirectory::getAttribP (const char* name) {

  if (attribs)
      for (int i=0; attribs[i].ignore!=-1; i++) {
          // Yeah, self made comparison!
          const char *n = name;
          const char *a = attribs[i].name;
          while (*n && *a && *n==*a) { n++; a++; };
          if (!*a && (!*n || *n=='/')) {
              // we reached the end of the subpart of name and the end of attribs->name, so they match
              if (*n=='/') {
                  Tag* tag = getTag (attribs[i].ID);
                  TagDirectory *tagDir;
                  if (attribs[i].subdirAttribs && tag && (tagDir=tag->getDirectory()))
                      return tagDir->getAttribP(n+1);
                  else
                      return NULL;
              }
              else
                  return &attribs[i];
          }
      }
  return NULL;
}

void TagDirectory::printAll (unsigned int level) const {

  // set the spacer prefix string
  char prefixStr[level*4+1];
  unsigned int i;
  for (i=0; i<level*4; i++)
    prefixStr[i] = ' ';
  prefixStr[i] = '\0';

  // recursively iterate over the tag list
  for (size_t i=0; i<tags.size(); i++) {
    std::string name = tags[i]->nameToString ();
    if (tags[i]->isDirectory())
        for (int j=0; tags[i]->getDirectory(j); j++) {
            printf ("%s+-- DIRECTORY %s[%d]:\n", prefixStr, name.c_str(), j);
            tags[i]->getDirectory(j)->printAll (level+1);
        }
    else
        std::string value = tags[i]->valueToString ();
  }
}

/** @brief Dump the TagDirectory and its sub-directories to the file 'fname'
 *
 * This method has been created to dump the metadata for the Custom Profile Builders.
 * It contains an [RT General] section to communicate some parameters, then the TagDirectory follows.
 *
 * The key is composed as follow: "010F_Make", i.e. "tag number or ID _ tag name"
 * Entries like:
 *
 * 927C_MakerNotesSony=$subdir
 *
 * indicates that this tag refer to a sub-directory. RT's Keywords begins with $, where & is the first char of the value.
 * $subdir is the only keyword so far.
 *
 * You'll have then to check for the [EXIF/927C_MakerNotesSony] section, given that the root section
 * is named [EXIF].
 *
 * WARNING: Some string will be sanitized, i.e. the new line char will be replaced by "\n". You'll
 * have to check for this escape string if you want a correct display of the value, but your KeyFile module
 * will most likely handle that automatically for you.
 *
 * @param commFNname Absolute path of the temporary communication file's name
 * @param commFNname Absolute path of the image's file name
 * @param commFNname Absolute path of the output profiles's file name
 * @param defaultPParams absolute or relative path (to the application's folder) of the default ProcParams to use
 * @param cfs pointer to a CacheImageData object that will contain common values
 * @param flagMode will tell whether the Custom Profile Builder is called for on flagging event or for real development
 * @param keyfile The KeyFile object to dump to. Has to be NULL (default value) on first call!
 * @param tagDirName Name of the current TagDirectory (full path, i.e. "EXIF/MakerNotes/LensInfo"). Can be empty on first call, "EXIF" will then be used
 *
 * @return True if everything went fine, false otherwise
 */
bool TagDirectory::CPBDump (const Glib::ustring &commFName, const Glib::ustring &imageFName, const Glib::ustring &profileFName, const Glib::ustring &defaultPParams, const CacheImageData* cfs, const bool flagMode,
                            rtengine::SafeKeyFile *keyFile, Glib::ustring tagDirName) const
{

    rtengine::SafeKeyFile *kf;
    if (!keyFile)
        kf = new rtengine::SafeKeyFile();
    else
        kf = keyFile;

    if (!kf)
        return false;

    if (!keyFile || tagDirName.empty())
        tagDirName = "EXIF";

    std::vector<const TagDirectory *> tagDirList;
    std::vector<Glib::ustring> tagDirPaths;

    FILE *f;
    if (!keyFile) {
        // open the file in write mode
        f = safe_g_fopen (commFName, "wt");
        if (f==NULL) {
            printf("TagDirectory::keyFileDump(\"%s\") >>> Error: unable to open file with write access!\n", commFName.c_str());
            delete kf;
            return false;
        }

        kf->set_string ("RT General", "CachePath", options.cacheBaseDir);
        kf->set_string ("RT General", "AppVersion", VERSION);
        kf->set_integer("RT General", "ProcParamsVersion", PPVERSION);
        kf->set_string ("RT General", "ImageFileName", imageFName);
        kf->set_string ("RT General", "OutputProfileFileName", profileFName);
        kf->set_string ("RT General", "DefaultProcParams", defaultPParams);
        kf->set_boolean("RT General", "FlaggingMode", flagMode);

        kf->set_double ("Common Data", "FNumber", cfs->fnumber);
        kf->set_double ("Common Data", "Shutter", cfs->shutter);
        kf->set_double ("Common Data", "FocalLength", cfs->focalLen);
        kf->set_integer("Common Data", "ISO", cfs->iso);
        kf->set_string ("Common Data", "Lens", cfs->lens);
        kf->set_string ("Common Data", "Make", cfs->camMake);
        kf->set_string ("Common Data", "Model", cfs->camModel);
    }

  // recursively iterate over the tag list
  for (size_t i=0; i<tags.size(); i++) {
      std::string tagName = tags[i]->nameToString ();
      if (tags[i]->isDirectory())
          for (int j=0; tags[i]->getDirectory(j); j++) {
              // Accumulating the TagDirectories to dump later
              tagDirPaths.push_back( Glib::ustring( tagDirName + "/" + getDumpKey(tags[i]->getID(), tagName) ) );
              tagDirList.push_back(tags[i]->getDirectory(j));
              kf->set_string (tagDirName, getDumpKey(tags[i]->getID(), tagName), "$subdir");
      }
      else {
          kf->set_string (tagDirName, getDumpKey(tags[i]->getID(), tagName), tags[i]->valueToString());
      }
  }

  // dumping the sub-directories
  for (size_t i=0; i< tagDirList.size(); i++)
      tagDirList.at(i)->CPBDump(commFName, imageFName, profileFName, defaultPParams, cfs, flagMode, kf, tagDirPaths.at(i));

  if (!keyFile) {
      fprintf (f, "%s", kf->to_data().c_str());
      fclose (f);
      delete kf;
  }

  return true;
}

Glib::ustring TagDirectory::getDumpKey (int tagID, const Glib::ustring tagName) {
    Glib::ustring key;
    if (options.CPBKeys == CPBKT_TID || options.CPBKeys == CPBKT_TID_NAME)
        key = Glib::ustring(Glib::ustring::format(std::fixed, std::hex, std::setfill(L'0'), std::setw(4), tagID));
    if (options.CPBKeys == CPBKT_TID_NAME)
        key += Glib::ustring("_");
    if (options.CPBKeys == CPBKT_TID_NAME || options.CPBKeys == CPBKT_NAME)
        key += Glib::ustring(tagName);
    return key;
}
void TagDirectory::addTag (Tag* tag) {

  // look up if it already exists:
  if (getTag (tag->getID()))
    delete tag;
  else
    tags.push_back (tag);
}

void TagDirectory::addTagFront (Tag* tag) {

  // look up if it already exists:
  if (getTag (tag->getID()))
    delete tag;
  else
    tags.insert (tags.begin(), tag);
}

void TagDirectory::replaceTag (Tag* tag) {

  // look up if it already exists:
  for (size_t i=0; i<tags.size(); i++)
    if (tags[i]->getID()==tag->getID()) {
      delete tags[i];
      tags[i] = tag;
      return;
    }
  tags.push_back (tag);
}

Tag* TagDirectory::getTag (int ID) const {

  for (size_t i=0; i<tags.size(); i++)
    if (tags[i]->getID()==ID)
      return tags[i];
  return NULL;
}

Tag* TagDirectory::getTag (const char* name) const {

  if (attribs) {
    for (int i=0; attribs[i].ignore!=-1; i++)
        if (!strcmp (attribs[i].name, name))
            return getTag (attribs[i].ID);
  }
  return NULL;
}

Tag* TagDirectory::getTagP (const char* name) const {

  if (attribs)
      for (int i=0; attribs[i].ignore!=-1; i++) {
          // Yeah, self made comparison!
          const char *n = name;
          const char *a = attribs[i].name;
          while (*n && *a && *n==*a) { n++; a++; };
          if (!*a && (!*n || *n=='/')) {
              // we reached the end of the subpart of name and the end of attribs->name, so they match
              if (*n=='/') {
                  Tag* tag = getTag (attribs[i].ID);
                  TagDirectory *tagDir;
                  if (attribs[i].subdirAttribs && tag && (tagDir=tag->getDirectory()))
                      return tagDir->getTagP(n+1);
                  else
                      return NULL;
              }
              else
                  return getTag (attribs[i].ID);
          }
      }
  return NULL;
}

Tag* TagDirectory::findTag (const char* name) const {
  if (attribs) {
    for (int i=0; attribs[i].ignore!=-1; i++)
        if (!strcmp (attribs[i].name, name)){
            Tag* t= getTag (attribs[i].ID);
            if(t) return t;
            else break;
        }
  }
  for (size_t i=0; i<tags.size(); i++)
     if(tags[i]->isDirectory()){
     TagDirectory *dir = tags[i]->getDirectory();
     Tag* t=dir->findTag(name);
     if(t) return t;
     }
  return NULL;
}

// Searches a simple value, as either attribute or element
// only for simple values, not for entries with special chars or free text
bool TagDirectory::getXMPTagValue(const char* name, char* value) const {
    *value=0;

    if (!getTag("ApplicationNotes")) return false;
    char *sXMP = (char*)getTag("ApplicationNotes")->getValue();

    // Check for full word
    char *pos=sXMP;

    bool found=false;
    do {
        pos=strstr(pos,name);
        if (pos) {
            char nextChar=*(pos+strlen(name));
            if (nextChar==' ' || nextChar=='>' || nextChar=='=') 
                found=true;
            else
                pos+=strlen(name);
        }
    } while (pos && !found);
    if (!found) return false;

    char *posTag =strchr(pos,'>');
    char *posAttr=strchr(pos,'"');

    if (!posTag && !posAttr) return false;

    if (posTag && (!posAttr || posTag<posAttr)) {
        // Tag
        pos=strchr(posTag+1,'<');
        strncpy(value,posTag+1,pos-posTag-1);
        value[pos-posTag-1]=0;
        return true;
    } else
    if (posAttr && (!posTag || posAttr<posTag)) {
        // Attribute
        pos=strchr(posAttr+1,'"');
        strncpy(value,posAttr+1,pos-posAttr-1);
        value[pos-posAttr-1]=0;
        return true;
    } else return false;
}

void TagDirectory::keepTag (int ID) {
    for (size_t i=0; i<tags.size(); i++)
        if (tags[i]->getID()==ID) tags[i]->setKeep(true);
}

int TagDirectory::calculateSize () {

  int size = 2; // space to store the number of tags
  for (size_t i=0; i<tags.size(); i++)
    if (tags[i]->getKeep()) 
        size += 12 + tags[i]->calculateSize ();
    
  size += 4; // next ifd pointer
  return size;
}
 
TagDirectory* TagDirectory::clone (TagDirectory* parent) {
    
    TagDirectory* td = new TagDirectory (parent, attribs, order);
    for (size_t i=0; i<tags.size(); i++)
        td->tags.push_back (tags[i]->clone (td));
    return td;
}

int TagDirectory::write (int start, unsigned char* buffer) {

    int size = calculateSize ();
    int tagnum = 0;
    int nondirspace = 0;
    for (size_t i=0; i<tags.size(); i++)
        if (tags[i]->getKeep()) {
            tagnum++;
            if (!tags[i]->isDirectory())
                nondirspace += tags[i]->calculateSize();
        }
    int nextValOffs = start + 2 + tagnum * 12 + 4;
    int nextDirOffs = nextValOffs + nondirspace;
    int pos = start;
    sset2 (tagnum, buffer+start, order);
    pos += 2;
    int maxPos = start + size;
    for (size_t i=0; i<tags.size(); i++) {
        if (tags[i]->getKeep()) {
            if (!tags[i]->isDirectory())
                nextValOffs = tags[i]->write (pos, nextValOffs, buffer);  // pos: where to put the tag, dataoffset: the place where the value can be put. return: next data offset
            else
                nextDirOffs = tags[i]->write (pos, nextDirOffs, buffer);  // pos: where to put the tag, dataoffset: the place where the value can be put. return: next data offset
            
            pos += 12;
        }
    }
    sset4 (0, buffer+pos, order);
    return maxPos;
}

void TagDirectory::applyChange (std::string name, std::string value) {

    std::string::size_type dp = name.find_first_of ('.');
    std::string fseg = name.substr (0,dp);
    // this is a final segment: apply change
    if (dp==std::string::npos) {

        Tag* t = NULL;
        for (size_t i=0; i<tags.size(); i++)
            if (tags[i]->nameToString()==fseg) {
                t = tags[i];
                break;
            }
        
        if (value=="#keep" && t)
            t->setKeep (true);
        else if (value=="#delete" && t) 
            t->setKeep (false);
        else if (t && !t->isDirectory()) 
            t->valueFromString (value);
        else {
            const TagAttrib* attrib = NULL;
            for (int i=0; attribs[i].ignore!=-1; i++)
                if (!strcmp (attribs[i].name, fseg.c_str())) {
                    attrib = &attribs[i];
                    break;
                }            
            if (attrib) {
                Tag* nt = new Tag (this, attrib);
                nt->initString (value.c_str());
                addTag (nt);
            }
        }
    }
    // this is a subdirectory
    else {
        // try to find it
        std::string::size_type dp1 = fseg.find_first_of ('[');
        std::string basename = fseg.substr (0,dp1);
        Tag* t = NULL;
        int dirnum = -1;
        for (size_t i=0; i<tags.size(); i++)
            if (tags[i]->isDirectory()) {
                for (int j=0; tags[i]->getDirectory(j); j++) {
                    if (tags[i]->nameToString(j) == fseg) {
                        t = tags[i];
                        dirnum = j;
                        break;
                    }
                }
                if (!t && tags[i]->nameToString() == basename) { // found it, but that directory index does not exist
                    t = tags[i];
                    dirnum = -1;
                }
            }
        if (!t && value!="#keep" && value!="#delete") {
            const TagAttrib* attrib = NULL;
            for (int i=0; attribs[i].ignore!=-1; i++)
                if (!strcmp (attribs[i].name, fseg.c_str())) {
                    attrib = &attribs[i];
                    break;
                }            
            if (attrib && attrib->subdirAttribs) {
                t = new Tag (this, attrib);
                t->initSubDir ();
                addTag (t);
            }
            dirnum = 0;
        }
        if (t && dirnum>=0)
            t->getDirectory(dirnum)->applyChange (name.substr (dp+1, std::string::npos), value);
    }
}

TagDirectoryTable::TagDirectoryTable ()
:zeroOffset(0),valuesSize(0)
{
}

TagDirectoryTable::TagDirectoryTable (TagDirectory* p, unsigned char *v,int memsize,int offs, TagType type, const TagAttrib* ta, ByteOrder border)
:TagDirectory(p,ta,border),zeroOffset(offs),valuesSize(memsize),defaultType( type )
{
    values = new unsigned char[valuesSize];
    memcpy(values,v,valuesSize);
    for( const TagAttrib* tattr = ta; tattr->ignore != -1; tattr++){
        Tag* newTag = new Tag (this, tattr, (values + zeroOffset+ tattr->ID*getTypeSize(type)), tattr->type == AUTO ? type : tattr->type);
        tags.push_back(newTag); // Here we can insert more tag in the same offset because of bitfield meaning
    }
}

TagDirectoryTable::TagDirectoryTable (TagDirectory* p, FILE* f, int memsize,int offs, TagType type, const TagAttrib* ta, ByteOrder border)
:TagDirectory(p,ta,border),zeroOffset(offs),valuesSize(memsize),defaultType( type )
{
    values = new unsigned char[valuesSize];
    fread (values, 1, valuesSize, f);

    for( const TagAttrib* tattr = ta; tattr->ignore != -1; tattr++){
        Tag* newTag = new Tag (this, tattr, (values + zeroOffset+ tattr->ID*getTypeSize(type)), tattr->type == AUTO ? type : tattr->type);
        tags.push_back(newTag); // Here we can insert more tag in the same offset because of bitfield meaning
    }
}
TagDirectory* TagDirectoryTable::clone (TagDirectory* parent) {

    TagDirectory* td = new TagDirectoryTable (parent,values,valuesSize,zeroOffset,defaultType, attribs, order);
    return td;
}

TagDirectoryTable::~TagDirectoryTable()
{
    if(values)
        delete [] values;
}
int TagDirectoryTable::calculateSize ()
{
    return valuesSize;
}

int TagDirectoryTable::write (int start, unsigned char* buffer) {
    if( values && valuesSize){
        memcpy(buffer+start,values,valuesSize);
        return start+valuesSize;
    }else
        return start;
}

//--------------- class Tag ---------------------------------------------------
// this class represents a tag stored in the directory
//-----------------------------------------------------------------------------

Tag::Tag (TagDirectory* p, FILE* f, int base) 
  : type(INVALID), count(0), value(NULL), allocOwnMemory(true), attrib(NULL), parent(p), directory(NULL) {

    ByteOrder order = getOrder();

    tag   = get2 (f, order);
    type  = (TagType)get2 (f, order);
    count = get4 (f, order);

    makerNoteKind = NOMK;
    keep = false;

    // filter out invalid tags
    // note the large count is to be able to pass LeafData ASCII tag which can be up to almost 10 megabytes,
    // (only a small part of it will actually be parsed though)
    if ((int)type<1 || (int)type>14 || count>10*1024*1024) {
        type = INVALID;
        return;
    }

    // store next Tag's position in file
    int save = ftell(f) + 4;

    // load value field (possibly seek before)
    valuesize = count * getTypeSize(type);

    if (valuesize > 4) 
        fseek (f, get4(f, getOrder()) + base, SEEK_SET);

    attrib = parent->getAttrib (tag);

    if (attrib && (attrib->action==AC_WRITE || attrib->action==AC_NEW))
        keep = true;

    if( tag == 0xc634 ){ // DNGPrivateData
        int currPos = ftell(f);
        char buffer[32],*p=buffer;
        while( fread (p, 1, 1, f ) && *p != 0 && p-buffer<sizeof(buffer)-1 )p++;
        *p=0;
        if( !strncmp(buffer,"Adobe",5) ){
            fread (buffer, 1, 14, f );
            if( !strncmp( buffer,"MakN",4) ){
                ByteOrder bom = ((buffer[8]=='M' && buffer[9]=='M')?MOTOROLA:INTEL) ;
                Tag* tmake = parent->getRoot()->findTag("Make");
                std::string make( tmake ? tmake->valueToString():"");
                int save = ftell(f);
                int originalOffset = sget4( (unsigned char*)&buffer[10], ( make.find("SONY") != std::string::npos ) || ( make.find("Canon") != std::string::npos ) || ( make.find("OLYMPUS") != std::string::npos ) ?MOTOROLA:bom );

                if( !parseMakerNote(f, save - originalOffset , bom ))
                    type = INVALID;
            }
        }else if( !strncmp(buffer,"PENTAX",6) ){
            makerNoteKind = HEADERIFD;
            fread (buffer, 1, 2, f);
            directory = new TagDirectory*[2];
            directory[0] = new TagDirectory (parent, f, currPos, pentaxAttribs, strncmp(buffer,"MM",2)? INTEL:MOTOROLA);
            directory[1] = NULL;
         }else
             /* SONY uses this tag to write hidden info and pointer to private encrypted tags
          {
              unsigned offset =sget4((unsigned char*)buffer, order);
              fseek(f,offset,SEEK_SET);
              makerNoteKind = TABLESUBDIR;
              directory = new TagDirectory*[2];
              directory[0] = new TagDirectory (parent, f, base, sonyDNGMakerNote, order);
              directory[1] = NULL;
              fseek (f, save, SEEK_SET);
              return;
          }*/
              type = INVALID;
    }
    // if this tag is the makernote, it needs special treatment (brand specific parsing)
    if (tag==0x927C && attrib && !strcmp (attrib->name, "MakerNote") ) {
        if( !parseMakerNote(f,base,order )){
            type = INVALID;
            fseek (f, save, SEEK_SET);
            return;
        }
    }
    else if (attrib && attrib->subdirAttribs) {
      // Some subdirs are specific of maker and model
      char make[128], model[128];
      make[0]=0;
      model[0]=0;
      Tag* tmake = parent->getRoot()->getTag ("Make");
      if (tmake) tmake->toString (make);
      Tag* tmodel = parent->getRoot()->getTag ("Model");
      if (tmodel) tmodel->toString (model);
          if (!strncmp(make, "SONY", 4)) {
              switch( tag ){
              case 0x0010:
                  directory = new TagDirectory*[2];
                  directory[1] = NULL;
                  if (count == 15360)
                      directory[0] = new TagDirectoryTable (parent, f, valuesize,0,BYTE , sonyCameraInfoAttribs, order);
                  else
                      directory[0] = new TagDirectoryTable (parent, f, valuesize,0,BYTE , sonyCameraInfo2Attribs, order);
                  break;
              case 0x0114:
                  directory = new TagDirectory*[2];
                  directory[1] = NULL;
                  if (count == 280 || count == 364)
                      directory[0] = new TagDirectoryTable (parent, f, valuesize,0,SHORT , sonyCameraSettingsAttribs, MOTOROLA);
                  else if (count == 332)
                      directory[0] = new TagDirectoryTable (parent, f, valuesize,0,SHORT , sonyCameraSettingsAttribs2, MOTOROLA);
                  else if(count == 1536 || count == 2048)
                      directory[0] = new TagDirectoryTable (parent, f, valuesize,0,BYTE  , sonyCameraSettingsAttribs3, INTEL);
                  else {
                      // Unknown CameraSettings
                      delete directory;
                      directory = NULL;
                  }
                  makerNoteKind = TABLESUBDIR;
                  break;
              case 0x9405:
                  directory = new TagDirectory*[2];
                  directory[1] = NULL;
                  directory[0] = new TagDirectoryTable (parent, f, valuesize,0,SHORT , attrib->subdirAttribs, order);
                  makerNoteKind = TABLESUBDIR;
                  break;
              default:
                  goto defsubdirs;
              }
          }else if (!strncmp(make, "PENTAX", 6)) {
              switch( tag ){
              case 0x005c:
              case 0x0205:
              case 0x0206:
              case 0x0208:
              case 0x0216:
                  directory = new TagDirectory*[2];
                  directory[1] = NULL;
                  directory[0] = new TagDirectoryTable (parent, f, valuesize,0,BYTE , attrib->subdirAttribs, order);
                  makerNoteKind = TABLESUBDIR;
                  break;
              case 0x0215:
                  directory = new TagDirectory*[2];
                  directory[1] = NULL;
                  directory[0] = new TagDirectoryTable (parent, f, valuesize,0,LONG , attrib->subdirAttribs, order);
                  makerNoteKind = TABLESUBDIR;
                  break;
              case 0x0207:
              {   // There are 2 format pentaxLensDataAttribs
                  int offsetFirst = 4;  // LensInfo2
                  if( strstr(model, "*ist") || strstr(model, "GX-1") || strstr(model, "K100D") || strstr(model, "K110D") )
                      offsetFirst = 3;  // LensInfo
                  else if( strstr(model, "645D") )
                      offsetFirst = 13;  // LensInfo3
                  else if( strstr(model, "K-5") || strstr(model, "K-r") )
                      offsetFirst = 12;  // LensInfo4
                  else if( strstr(model, "K-01") )
                      offsetFirst = 15;  // LensInfo5
                  directory = new TagDirectory*[2];
                  directory[1] = NULL;
                  directory[0] = new TagDirectoryTable (parent, f, valuesize,offsetFirst,BYTE , attrib->subdirAttribs, order);
                  makerNoteKind = TABLESUBDIR;
              }
                  break;
              case 0x0239:
                  directory = new TagDirectory*[2];
                  directory[1] = NULL;
                  directory[0] = new TagDirectoryTable (parent, f, valuesize,0,BYTE , attrib->subdirAttribs, order);
                  makerNoteKind = TABLESUBDIR;
                  break;
              default:
                  goto defsubdirs;
              }
          }else if (!strncmp(make, "Canon", 5)) {
              switch( tag ){
              case 0x0001:
              case 0x0002:
              case 0x0004:
              case 0x0005:
              case 0x0093:
              case 0x0098:
              case 0x00a0:
                  directory = new TagDirectory*[2];
                  directory[1] = NULL;
                  directory[0] = new TagDirectoryTable (parent, f, valuesize,0,SSHORT , attrib->subdirAttribs, order);
                  makerNoteKind = TABLESUBDIR;
                  break;
              case 0x009a:
              case 0x4013:
                  directory = new TagDirectory*[2];
                  directory[1] = NULL;
                  directory[0] = new TagDirectoryTable (parent, f, valuesize,0,LONG , attrib->subdirAttribs, order);
                  makerNoteKind = TABLESUBDIR;
                  break;
              default:
                  goto defsubdirs;
              }
          }else if (!strncmp(make, "NIKON", 5)) {
              switch (tag) {
              case 0x0025: {
                  directory = new TagDirectory*[2];
                  directory[1] = NULL;
                  directory[0] = new TagDirectoryTable (parent, f, valuesize,0,BYTE , attrib->subdirAttribs, order);
                  makerNoteKind = TABLESUBDIR;
                  break;
              }
              default:
                  goto defsubdirs;
              }
          }else if(type==UNDEFINED){
              count = 1;
              type = LONG;
              directory = new TagDirectory*[2];
              directory[0] = new TagDirectory (parent, f, base, attrib->subdirAttribs, order);
              directory[1] = NULL;
          }else
              goto defsubdirs;
    }
    else {
      // read value
      value = new unsigned char [valuesize];
      fread (value, 1, valuesize, f);
    }
    // seek back to the saved position
    fseek (f, save, SEEK_SET);
    return;

defsubdirs:
    // read value
    value = new unsigned char [valuesize];
    fread (value, 1, valuesize, f);
    int pos = ftell (f);
    // count the number of valid subdirs
    int sdcount = count;
    if (sdcount>0) {
        if (parent->getAttribTable()==olympusAttribs)
            sdcount = 1;
        // allocate space
        directory = new TagDirectory*[sdcount+1];
        // load directories
        for (size_t j=0,i=0; j<count; j++,i++) {
            int newpos = base + toInt(j*4, LONG);
            fseek (f, newpos, SEEK_SET);
            directory[i] = new TagDirectory (parent, f, base, attrib->subdirAttribs, order);
            fseek (f, pos, SEEK_SET);
        }
        // set the terminating NULL
        directory[sdcount] = NULL;
     }
     else
        type = INVALID;

    // seek back to the saved position
    fseek (f, save, SEEK_SET);
    return;

}

bool Tag::parseMakerNote(FILE* f, int base, ByteOrder bom )
{
    value = NULL;
    Tag* tmake = parent->getRoot()->findTag("Make");
    std::string make( tmake ? tmake->valueToString():"");

    Tag* tmodel = parent->getRoot()->findTag ("Model");
    std::string model( tmodel ? tmodel->valueToString():"");

    if ( make.find( "NIKON" ) != std::string::npos ) {
        if ( model.find("NIKON E700")!= std::string::npos ||
             model.find("NIKON E800")!= std::string::npos ||
             model.find("NIKON E900")!= std::string::npos ||
             model.find("NIKON E900S")!= std::string::npos ||
             model.find("NIKON E910")!= std::string::npos ||
             model.find("NIKON E950")!= std::string::npos ) {
            makerNoteKind = HEADERIFD;
            valuesize = 8;
            value = new unsigned char[8];
            fread (value, 1, 8, f);
            directory = new TagDirectory*[2];
            directory[0] = new TagDirectory (parent, f, base, nikon2Attribs, bom);
            directory[1] = NULL;
        } else if ( model.find("NIKON E990")!= std::string::npos ||
                   (model.find("NIKON D1")!= std::string::npos && model.size()>8 && model.at(8)!='0')) {
            makerNoteKind = IFD;
            directory = new TagDirectory*[2];
            directory[0] = new TagDirectory (parent, f, base, nikon3Attribs, bom);
            directory[1] = NULL;
        } else {
            // needs refinement! (embedded tiff header parsing)
            makerNoteKind = NIKON3;
            valuesize = 18;
            value = new unsigned char[18];
            int basepos = ftell (f);
            fread (value, 1, 18, f);
            directory = new TagDirectory*[2];
            directory[0] = new TagDirectory (parent, f, basepos+10, nikon3Attribs, bom);
            directory[1] = NULL;
        }
    } else if ( make.find( "Canon" ) != std::string::npos  ) {
        makerNoteKind = IFD;
        directory = new TagDirectory*[2];
        directory[0] = new TagDirectory (parent, f, base, canonAttribs, bom);
        directory[1] = NULL;
    } else if ( make.find( "PENTAX" ) != std::string::npos ) {
        makerNoteKind = HEADERIFD;
        valuesize = 6;
        value = new unsigned char[6];
        fread (value, 1, 6, f);
        directory = new TagDirectory*[2];
        directory[0] = new TagDirectory (parent, f, base, pentaxAttribs, bom);
        directory[1] = NULL;
    } else if ( make.find( "FUJIFILM" ) != std::string::npos ) {
        makerNoteKind = FUJI;
        valuesize = 12;
        value = new unsigned char[12];
        fread (value, 1, 12, f);
        directory = new TagDirectory*[2];
        directory[0] = new TagDirectory (parent, f, ftell(f)-12, fujiAttribs, INTEL);
        directory[1] = NULL;
    } else if ( make.find( "KONICA MINOLTA" ) != std::string::npos || make.find( "Minolta" ) != std::string::npos ) {
        makerNoteKind = IFD;
        directory = new TagDirectory*[2];
        directory[0] = new TagDirectory (parent, f, base, minoltaAttribs, bom);
        directory[1] = NULL;
    } else if ( make.find( "SONY" ) != std::string::npos ) {
        valuesize = 12;
        value = new unsigned char[12];
        fread (value, 1, 12, f);
        if (!strncmp((char*)value, "SONY DSC", 8))
            makerNoteKind = HEADERIFD;
        else {
            makerNoteKind = IFD;
            fseek (f, -12, SEEK_CUR);
        }
        directory = new TagDirectory*[2];
        directory[0] = new TagDirectory (parent, f, base, sonyAttribs, bom );
        directory[1] = NULL;
    } else if ( make.find( "OLYMPUS" ) != std::string::npos ) {
        makerNoteKind = HEADERIFD;
        valuesize = 8;
        value = new unsigned char[12];
        fread (value, 1, 8, f);
        directory = new TagDirectory*[2];
        directory[1] = NULL;
        if (!strncmp((char*)value, "OLYMPUS", 7)) {
            makerNoteKind = OLYMPUS2;
            fread (value+8, 1, 4, f);
            valuesize = 12;
            directory[0] = new TagDirectory (parent, f, ftell(f)-12, olympusAttribs, value[8]=='I' ? INTEL : MOTOROLA);
        } else
            directory[0] = new TagDirectory (parent, f, base, olympusAttribs, bom);
    } else
        return false;
    return true;
}

Tag* Tag::clone (TagDirectory* parent) {

    Tag* t = new Tag (parent, attrib);
    
    t->tag = tag;
    t->type = type;
    t->count = count;
    t->keep = keep;
    t->valuesize = valuesize;
    if (value) {
        t->value = new unsigned char [valuesize];
        memcpy (t->value, value, valuesize);
    }
    else
        value = NULL;
    t->makerNoteKind = makerNoteKind;
    if (directory) {
        int ds = 0;
        for (; directory[ds]; ds++);
        t->directory = new TagDirectory*[ds+1];
        for (int i=0; i<ds; i++)
            t->directory[i] = directory[i]->clone (parent);
        t->directory[ds] = NULL;
    }
    else
        t->directory = NULL;
    return t;
}

Tag::~Tag () {

  // delete value
  if (value && allocOwnMemory)
    delete [] value;
  
  // if there are directories behind the tag, delete them
  int i = 0;
  if (directory) {
    while (directory[i])
        delete directory[i++];
    delete [] directory;
  }  
}

void Tag::setInt (int v, int ofs, TagType astype) {

    if (astype==SHORT)
        sset2 (v, value+ofs, getOrder());
    else if (astype==RATIONAL) {
        sset4 (v, value+ofs, getOrder());
        sset4 (1, value+ofs+4, getOrder());
    }
    else
        sset4 (v, value+ofs, getOrder());
}

void Tag::fromInt (int v) {

    if (type==SHORT)
        sset2 (v, value, getOrder());
    else
        sset4 (v, value, getOrder());
}

void Tag::fromString (const char* v, int size) {

    if( value && allocOwnMemory)
        delete [] value;
    if (size<0)
        valuesize = strlen (v) + 1;
    else
        valuesize = size;
    count = valuesize;
    if( allocOwnMemory )
       value = new unsigned char [valuesize];
    memcpy ((char*)value, v, valuesize);
}

int Tag::toInt (int ofs, TagType astype) {
  if (attrib)
    return attrib->interpreter->toInt(this, ofs, astype);

  int a;
  if (astype == INVALID)
    astype = type;
  switch (astype) {
    case BYTE:  return value[ofs];
    case ASCII: return 0;
    case SSHORT:return (int)int2_to_signed(sget2 (value+ofs, getOrder())); 
    case SHORT: return (int)sget2 (value+ofs, getOrder());
    case SLONG:  
    case LONG:  return (int)sget4 (value+ofs, getOrder());
    case SRATIONAL: 
    case RATIONAL: a = (int)sget4 (value+ofs+4, getOrder()); return a==0 ? 0 : (int)sget4 (value+ofs, getOrder()) / a;
    case FLOAT:     return (int)toDouble(ofs);
    case UNDEFINED: return 0;
    default: return 0; // Quick fix for missing cases (INVALID, DOUBLE, OLYUNDEF, SUBDIR)
  }
  return 0;
}

double Tag::toDouble (int ofs) {
  if (attrib)
    return attrib->interpreter->toDouble(this, ofs);

  union IntFloat { uint32_t i; float f; } conv;

  double ud, dd;
  switch (type) {
    case BYTE:  return (double)((int)value[ofs]);
    case ASCII: return 0.0;
    case SSHORT:return (double)int2_to_signed(sget2 (value+ofs, getOrder()));  
    case SHORT: return (double)((int)sget2 (value+ofs, getOrder()));
    case SLONG:  
    case LONG:  return (double)((int)sget4 (value+ofs, getOrder()));
    case SRATIONAL: 
    case RATIONAL: ud = (int)sget4 (value+ofs, getOrder()); dd = (int)sget4 (value+ofs+4, getOrder()); return dd==0. ? 0. : (double)ud / (double)dd;
    case FLOAT:     
        conv.i=sget4 (value+ofs, getOrder());
        return conv.f;  // IEEE FLOATs are already C format, they just need a recast

    case UNDEFINED: return 0.;
    default: return 0.; // Quick fix for missing cases (INVALID, DOUBLE, OLYUNDEF, SUBDIR)
  }
  return 0.;
}

/**
 * @brief Create an array of the elements
 */
double *Tag::toDoubleArray(int ofs) {
    double *values = new double[count];
    for (int i=0; i<count; ++i) {
        values[i] = toDouble(ofs+i*getTypeSize(type));
    }
    return values;
}

void Tag::toRational (int& num, int& denom, int ofs) {

  switch (type) {
    case BYTE:   num = (int)value[ofs]; denom = 1; break;
    case ASCII:  num = 0; denom = 0; break;
    case SSHORT: 
    case SHORT:  num = (int)sget2 (value+ofs, getOrder()); denom = 1; break;
    case SLONG:  
    case LONG:   num = (int)sget4 (value+ofs, getOrder()); denom = 1; break;
    case SRATIONAL: 
    case RATIONAL: num = (int)sget4 (value+ofs, getOrder()); denom = (int)sget4 (value+ofs+4, getOrder()); break;
    case FLOAT:   num = 0; denom = 0; break;
    case UNDEFINED: num = 0; denom = 0; break;
    default: num = 0; denom = 0; break; // Quick fix for missing cases (INVALID, DOUBLE, OLYUNDEF, SUBDIR)
  }
}

void Tag::toString (char* buffer, int ofs) {

  if (type==UNDEFINED && !directory) {
      bool isstring = true;
      int i=0;
      for (i=0; i+ofs<count && i<64 && value[i+ofs]; i++)
        if (value[i+ofs]<32 || value[i+ofs]>126)
            isstring = false;
      if (isstring) {
        int j = 0;
        for (i=0; i+ofs<count && i<64 && value[i+ofs]; i++) {
            if (value[i+ofs]=='<' || value[i+ofs]=='>')
                buffer[j++] = '\\';
            buffer[j++] = value[i+ofs];
        }
        buffer[j++] = 0;     
        return;
      }
  }
  else if (type==ASCII) {
    sprintf (buffer, "%.64s", value+ofs);
    return; 
  }

  size_t maxcount = 4;
  if (count<4)
    maxcount = count;
  
  strcpy (buffer, "");
  for (size_t i=0; i<maxcount; i++) {
    if (i>0)
        strcat (buffer, ", ");
    char* b = buffer + strlen(buffer);
        
    switch (type) {
        case UNDEFINED: 
        case BYTE:  sprintf (b, "%d", value[i+ofs]); break;
        case SSHORT: sprintf (b, "%d", toInt(2*i+ofs)); break;
        case SHORT: sprintf (b, "%u", toInt(2*i+ofs)); break;
        case SLONG: sprintf (b, "%ld", (long)toInt(4*i+ofs)); break;
        case LONG:  sprintf (b, "%lu", (unsigned long)toInt(4*i+ofs)); break;
        case SRATIONAL: 
        case RATIONAL: sprintf (b, "%d/%d", (int)sget4 (value+8*i+ofs, getOrder()), (int)sget4 (value+8*i+ofs+4, getOrder())); break; 
        case FLOAT:    sprintf (b, "%g", toDouble(8*i+ofs)); break;
    default: break;
    }
  }
  if (count > maxcount)
    strcat (buffer, "...");
}

std::string Tag::nameToString (int i) {

    char buffer[1024];
    if (attrib) 
        strcpy (buffer, attrib->name);
    else 
        sprintf (buffer, "0x%x", tag);
    if (i>0)
        sprintf (buffer+strlen(buffer)-1, "[%d]", i);
    return buffer;        
}

std::string Tag::valueToString () {

    char buffer[1024];
    if (attrib && attrib->interpreter) 
        return attrib->interpreter->toString (this);
    else {
        toString (buffer);
        return buffer;
    }
}

void Tag::valueFromString (const std::string& value) {

    if (attrib && attrib->interpreter) 
        attrib->interpreter->fromString (this, value);
}

int Tag::calculateSize () {
    int size = 0;
    
    if (directory) {
        int j;
        for (j=0; directory[j]; j++)
            size += directory[j]->calculateSize ();
        if (j>1)
            size += 4*j;
    }
    else if (valuesize > 4) 
      size += valuesize + (valuesize%2); // we align tags to even byte positions 

   if (makerNoteKind!=NOMK) 
        count = directory[0]->calculateSize () / getTypeSize(type);
        
   if (makerNoteKind==NIKON3 || makerNoteKind==OLYMPUS2 || makerNoteKind==FUJI) 
        size += valuesize;
   else if (makerNoteKind==HEADERIFD)
        size += valuesize;
    return size;
}    

int Tag::write (int offs, int dataOffs, unsigned char* buffer) {

  if ((int)type==0 || offs>65500)
    return dataOffs;

  sset2 (tag, buffer+offs, parent->getOrder());
  offs += 2;
  unsigned short typ = type;
  sset2 (typ, buffer+offs, parent->getOrder());
  offs += 2;
  sset4 (count, buffer+offs, parent->getOrder());
  offs += 4;
  if (!directory) {
    if (valuesize>4) {
        sset4 (dataOffs, buffer+offs, parent->getOrder());
        memcpy (buffer+dataOffs, value, valuesize);
        if (valuesize%2)
            buffer[dataOffs+valuesize] = 0; // zero padding required by the exif standard
        return dataOffs + valuesize + (valuesize%2);
    }
    else {
        memcpy (buffer+offs, value, valuesize);
        return dataOffs;
    }
  }
  else {
    if (makerNoteKind==NIKON3) {
        sset4 (dataOffs, buffer+offs, parent->getOrder());
        memcpy (buffer+dataOffs, value, 18);
        dataOffs += 10;
        dataOffs += directory[0]->write (8, buffer+dataOffs);
        return dataOffs;
    }
    else if (makerNoteKind==OLYMPUS2 || makerNoteKind==FUJI) {
        sset4 (dataOffs, buffer+offs, parent->getOrder());
        memcpy (buffer+dataOffs, value, valuesize);
        dataOffs += valuesize + directory[0]->write (valuesize, buffer+dataOffs);
        return dataOffs;
    }
    else if (makerNoteKind==HEADERIFD) {
        sset4 (dataOffs, buffer+offs, parent->getOrder());
        memcpy (buffer+dataOffs, value, valuesize);
        dataOffs += valuesize;
        dataOffs += directory[0]->write (dataOffs, buffer);
        return dataOffs;
    }
    else if( makerNoteKind==TABLESUBDIR){
        sset4 (dataOffs, buffer+offs, parent->getOrder());
        dataOffs = directory[0]->write (dataOffs, buffer);
        return dataOffs;
    }
    else if (!directory[1]) {
        sset4 (dataOffs, buffer+offs, parent->getOrder());
        return directory[0]->write (dataOffs, buffer);
    }
    else {
        sset4 (dataOffs, buffer+offs, parent->getOrder());
        int linkOffs = dataOffs;
        for (int i=0; directory[i]; i++)
            dataOffs += 4;              
        for (int i=0; directory[i]; i++) {
            sset4 (dataOffs, buffer+linkOffs, parent->getOrder());
            linkOffs += 4;
            dataOffs = directory[i]->write (dataOffs, buffer);
        }
        return dataOffs;
    }
  }
}

Tag::Tag (TagDirectory* p, const TagAttrib* attr)
 : tag(attr ? attr->ID : -1), type(INVALID), count(0), value(NULL), valuesize(0), keep(true), allocOwnMemory(true), attrib(attr), parent(p), directory(NULL), makerNoteKind (NOMK) {
}

Tag::Tag (TagDirectory* p, const TagAttrib* attr, int data, TagType t) 
 : tag(attr ? attr->ID : -1), type(t), count(1), value(NULL), valuesize(0), keep(true), allocOwnMemory(true), attrib(attr), parent(p), directory(NULL), makerNoteKind (NOMK) {

    initInt (data, t);
}

Tag::Tag (TagDirectory* p, const TagAttrib* attr, unsigned char *data, TagType t)
 : tag(attr ? attr->ID : -1), type(t), count(1), value(NULL), valuesize(0), keep(true), allocOwnMemory(false), attrib(attr), parent(p), directory(NULL), makerNoteKind (NOMK) {

    initType (data, t);
}

Tag::Tag (TagDirectory* p, const TagAttrib* attr, const char* text) 
 : tag(attr ? attr->ID : -1), type(ASCII), count(1), value(NULL), valuesize(0), keep(true), allocOwnMemory(true), attrib(attr), parent(p), directory(NULL), makerNoteKind (NOMK) {

    initString (text);
}

void Tag::initType (unsigned char *data, TagType type)
{
    valuesize = getTypeSize(type);
    if( allocOwnMemory ){
       value = new unsigned char[valuesize];
       memcpy ((char*)value, data, valuesize);
    }else
        value = data;
}

void Tag::initInt (int data, TagType t, int cnt) {
  
    type = t;
    if (t==LONG) 
        valuesize = 4;
    else if (t==SHORT) 
        valuesize = 2;
    else if (t==BYTE)
        valuesize = 1;
    else if (t==RATIONAL) 
        valuesize = 8;

    count = cnt;
    valuesize *= count;
    value = new unsigned char[valuesize];
    setInt (data, 0, t);
}
  
void Tag::initString (const char* text) {

    type = ASCII;
    count = strlen(text)+1;
    valuesize = count;
    value = new unsigned char[valuesize];
    strcpy ((char*)value, text);
}

void Tag::initSubDir () {
    type = LONG;
    valuesize = 4;
    count = 1;
    value = new unsigned char[4];
    setInt (0);
    directory = new TagDirectory*[2];
    directory[0] = new TagDirectory (parent, attrib ? attrib->subdirAttribs : NULL, parent->getOrder());
    directory[1] = NULL;
}

void Tag::initSubDir (TagDirectory* dir) {
    type = LONG;
    valuesize = 4;
    count = 1;
    value = new unsigned char[4];
    setInt (0);
    directory = new TagDirectory*[2];
    directory[0] = dir;
    directory[1] = NULL;
}

void Tag::initMakerNote (MNKind mnk, const TagAttrib* ta) {
    type = UNDEFINED;
    valuesize = 4;
    count = 1;
    value = new unsigned char[4];
    setInt (0);
    directory = new TagDirectory*[2];
    directory[0] = new TagDirectory (parent, ta, parent->getOrder());
    directory[1] = NULL;
    makerNoteKind = mnk;
}

void Tag::initUndefArray (const char* data, int len) {
    type = UNDEFINED;
    count = valuesize = len;
    value = new unsigned char[valuesize];
    memcpy (value, data, len);
}

void Tag::initLongArray (const char* data, int len) {
    type = LONG;
    count = (len+3)/4;
    valuesize = count * 4;
    value = new unsigned char[valuesize];
    memcpy (value, data, len);
}

void Tag::initRational (int num, int den) {
    count = 1;
    valuesize = 8;
    value = new unsigned char[8];
    type = RATIONAL;
    setInt (num, 0);
    setInt (den, 4);
}

//--------------- class IFDParser ---------------------------------------------
// static functions to read tag directories from different kinds of files
//-----------------------------------------------------------------------------


const TagAttrib* lookupAttrib (const TagAttrib* dir, const char* field) {

    for (int i=0; dir[i].ignore!=-1; i++)
        if (!strcmp (dir[i].name, field))
            return &dir[i];
    return 0;
}


TagDirectory* ExifManager::parseCIFF (FILE* f, int base, int length) {

    TagDirectory* root = new TagDirectory (NULL, ifdAttribs, INTEL);
    Tag* exif = new Tag (root, lookupAttrib(ifdAttribs,"Exif"));
    exif->initSubDir ();
    Tag* mn = new Tag (exif->getDirectory(), lookupAttrib(exifAttribs,"MakerNote"));
    mn->initMakerNote (IFD, canonAttribs);
    root->addTag (exif);
    exif->getDirectory()->addTag (mn);
    parseCIFF (f, base, length, root);
    root->sort ();
    return root;
}

Tag* ExifManager::saveCIFFMNTag (FILE* f, TagDirectory* root, int len, const char* name) {
    int s = ftell (f);
    char* data = new char [len];
    fread (data, len, 1, f);
    TagDirectory* mn = root->getTag ("Exif")->getDirectory()->getTag("MakerNote")->getDirectory();
    Tag* cs = new Tag (mn, lookupAttrib(canonAttribs, name));
    cs->initUndefArray (data, len);
    mn->addTag (cs);
    fseek (f, s, SEEK_SET);
    return cs;
}

void ExifManager::parseCIFF (FILE* f, int base, int length, TagDirectory* root) {

  char buffer[1024];
  Tag* t;

  fseek (f, base+length-4, SEEK_SET);

  int dirStart = get4 (f, INTEL) + base;
  fseek (f, dirStart, SEEK_SET);

  int numOfTags = get2 (f, INTEL);

  if (numOfTags > 100) return;
  
  float exptime, shutter, aperture, fnumber, ev;
  exptime = fnumber = shutter = aperture = ev = -1000.f;
  int focal_len, iso;
  focal_len = iso = -1;
  
  TagDirectory* exif = root->getTag("Exif")->getDirectory();
  
  time_t timestamp = time (NULL);
  
  for (int i=0; i<numOfTags; i++) {

    int type = get2 (f, INTEL);
    int len  = get4 (f, INTEL);
    int nextPos = ftell (f) + 4;
    
    // seek to the location of the value
    fseek (f, base + get4 (f, INTEL), SEEK_SET);
  
    if ((((type >> 8) + 8) | 8) == 0x38)
      parseCIFF (f, ftell(f), len, root);	// Parse a sub-table 
     
    if (type == 0x0810) {
      fread (buffer, 64, 1, f);
      t = new Tag (root, lookupAttrib(ifdAttribs,"Artist"));
      t->initString (buffer);
      root->addTag (t);
    }
    if (type == 0x080a) {
      fread (buffer, 64, 1, f);
      t = new Tag (root, lookupAttrib(ifdAttribs,"Make"));
      t->initString (buffer);
      root->addTag (t);
      fseek (f, strlen(buffer) - 63, SEEK_CUR);
      fread (buffer, 64, 1, f);
      t = new Tag (root, lookupAttrib(ifdAttribs,"Model"));
      t->initString (buffer);
      root->addTag (t);
    }
    if (type == 0x1818) {
      ev = int_to_float(get4(f, INTEL));
      shutter = int_to_float(get4(f, INTEL));
      exptime = pow (2, -shutter);
      aperture = int_to_float(get4(f, INTEL));
      fnumber = pow (2, aperture/2);
      
    }
    if (type == 0x102d) {
        Tag* t = saveCIFFMNTag (f, root, len, "CanonCameraSettings");
        int mm = t->toInt (34, SHORT);
        Tag* nt = new Tag (exif, lookupAttrib(exifAttribs,"MeteringMode"));
        switch (mm) {
            case 0: nt->initInt (5, SHORT); break;
            case 1: nt->initInt (3, SHORT); break;
            case 2: nt->initInt (1, SHORT); break;
            case 3: nt->initInt (5, SHORT); break;
            case 4: nt->initInt (6, SHORT); break;
            case 5: nt->initInt (2, SHORT); break;
        }
        exif->addTag (nt);
        nt = new Tag (exif, lookupAttrib(exifAttribs,"MaxApertureValue"));
        nt->initRational (t->toInt(52,SHORT), 32);
        exif->addTag (nt);
        int em = t->toInt(40,SHORT);
        nt = new Tag (exif, lookupAttrib(exifAttribs,"ExposureProgram"));
        switch (em) {
            case 0: nt->initInt (2, SHORT); break;
            case 1: nt->initInt (2, SHORT); break;
            case 2: nt->initInt (4, SHORT); break;
            case 3: nt->initInt (3, SHORT); break;
            case 4: nt->initInt (1, SHORT); break;
            default: nt->initInt (0, SHORT); break;
        }
        exif->addTag (nt);
        nt = new Tag (exif, lookupAttrib(exifAttribs,"Flash"));
        if (t->toInt(8,SHORT)==0)
            nt->initInt (0, SHORT);
        else
            nt->initInt (1, SHORT);
        exif->addTag (nt);        
        nt = new Tag (exif, lookupAttrib(exifAttribs,"MaxApertureValue"));
        nt->initRational (t->toInt(52,SHORT), 32);
        exif->addTag (nt);        
    }
    if (type == 0x1029) 
        saveCIFFMNTag (f, root, len, "CanonFocalLength");
    if (type == 0x1031) 
        saveCIFFMNTag (f, root, len, "SensorInfo");
    if (type == 0x1033) 
        saveCIFFMNTag (f, root, len, "CustomFunctions");
    if (type == 0x1038) 
        saveCIFFMNTag (f, root, len, "CanonAFInfo");
    if (type == 0x1093) 
        saveCIFFMNTag (f, root, len, "CanonFileInfo");
    if (type == 0x10a9) 
        saveCIFFMNTag (f, root, len, "ColorBalance");
    if (type == 0x102a) {
        saveCIFFMNTag (f, root, len, "CanonShotInfo");

        iso = pow (2, (get4(f, INTEL),get2(f, INTEL))/32.0 - 4) * 50;
        aperture  = (get2(f, INTEL),(short)get2(f, INTEL))/32.0f;
        fnumber = pow (2, aperture/2);
        shutter = ((short)get2(f, INTEL))/32.0f;
        ev = ((short)get2(f, INTEL))/32.0f;
        fseek (f, 34, SEEK_CUR);
        if (shutter > 1e6) shutter = get2 (f, INTEL) / 10.0f;
        exptime   = pow (2,-shutter);
    }
    if (type == 0x5029) {
      focal_len = len >> 16;
      if ((len & 0xffff) == 2) focal_len /= 32;
    }
//    if (type == 0x5813) flash_used = int_to_float(len);
    if (type == 0x580e) timestamp  = len;
    if (type == 0x180e) timestamp  = get4 (f, INTEL); 
    if ((type | 0x4000) == 0x580e) 
      timestamp = mktime (gmtime (&timestamp));
    fseek (f, nextPos, SEEK_SET);
  }
  if (shutter>-999) {
    t = new Tag (exif, lookupAttrib(exifAttribs,"ShutterSpeedValue"));
    t->initRational ((int)(shutter*10000), 10000);
    exif->addTag (t);
  }
  if (exptime>-999) {
    t = new Tag (exif, lookupAttrib(exifAttribs,"ExposureTime"));
    t->initRational ((int)(exptime*10000), 10000);
    exif->addTag (t);
  }
  if (aperture>-999) {
    t = new Tag (exif, lookupAttrib(exifAttribs,"ApertureValue"));
    t->initRational ((int)(aperture*10), 10);
    exif->addTag (t);
  }
  if (fnumber>-999) {
    t = new Tag (exif, lookupAttrib(exifAttribs,"FNumber"));
    t->initRational ((int)(fnumber*10), 10);
    exif->addTag (t);
  }
  if (ev>-999) {
    t = new Tag (exif, lookupAttrib(exifAttribs,"ExposureBiasValue"));
    t->initRational ((int)(ev*1000), 1000);
    exif->addTag (t);
  }
  if (iso>0) {
    t = new Tag (exif, lookupAttrib(exifAttribs,"ISOSpeedRatings"));
    t->initInt (iso, LONG);
    exif->addTag (t);
  }
  if (focal_len>0) {
    t = new Tag (exif, lookupAttrib(exifAttribs,"FocalLength"));
    t->initRational (focal_len*32, 32);
    exif->addTag (t);
  }

  if (timestamp!=time(NULL)) {
    struct tm* tim = localtime (&timestamp);
    strftime (buffer, 20, "%Y:%m:%d %H:%M:%S", tim);
    t = new Tag (exif, lookupAttrib(exifAttribs,"DateTimeOriginal"));
    t->initString (buffer);
    exif->addTag (t);
    t = new Tag (exif, lookupAttrib(exifAttribs,"DateTimeDigitized"));
    t->initString (buffer);
    exif->addTag (t);
    t = new Tag (root, lookupAttrib(ifdAttribs,"DateTime"));
    t->initString (buffer);
    root->addTag (t);
  }
}

static void
parse_leafdata(TagDirectory* root, ByteOrder order) {

    Tag *leafdata = root->getTag("LeafData");
    if (!leafdata) {
        return;
    }
    unsigned char *value = leafdata->getValue();
    int valuesize = leafdata->getValueSize();

    // parse LeafData tag, a tag specific to Leaf digital backs, and has a custom
    // format with 52 byte tag headers starting with "PKTS"
    const char *PKTS_tag = (order == MOTOROLA) ? "PKTS" : "STKP";
    char *hdr;
    int pos = 0;

    // There are lots of sub-tags in here, but for now we only care about those directly
    // useful to RT, which is ISO and rotation. Shutter speed and aperture is not
    // available here.
    int iso_speed = 0;
    int rotation_angle = 0;
    int found_count = 0;
    while (pos + sizeof(hdr) <= valuesize && found_count < 2) {
        hdr = (char *)&value[pos];
        if (strncmp(hdr, PKTS_tag, 4) != 0) {
            // in a few cases the header can be offset a few bytes, don't know why
            // it does not seem to be some sort of alignment, it appears random,
            // this check takes care of it, restart if we find an offset match.
            int offset = 1;
            for (; offset <= 3; offset++) {
                if (strncmp(&hdr[offset], PKTS_tag, 4) == 0) {
                    pos += offset;
                    break;
                }
            }
            if (offset <= 3) {
                continue;
            }
            break;
        }
        int size = sget4((unsigned char *)&hdr[48], order);
        if (pos + size > valuesize) {
            break;
        }
        pos += 52;
        char *val = (char *)&value[pos];
        if (strncmp(&hdr[8], "CameraObj_ISO_speed", 19) == 0) {
            iso_speed = 25 * (1 << (atoi(val) - 1));
            found_count++;
        } else if (strncmp(&hdr[8], "ImgProf_rotation_angle", 22) == 0) {
            rotation_angle = atoi(val);
            found_count++;
        } else {
            // check if this is a sub-directory, include test for that strange offset of next header
            if (size >= 8 &&
                (strncmp(val, PKTS_tag, 4) == 0 ||
                 strncmp(&val[1], PKTS_tag, 4) == 0 ||
                 strncmp(&val[2], PKTS_tag, 4) == 0 ||
                 strncmp(&val[3], PKTS_tag, 4) == 0))
            {
                // start of next hdr, this is a sub-directory, we skip those for now.
                size = 0;
            }
        }
        pos += size;
    }

    // create standard tags from the custom Leaf tags
    Tag* exif = root->getTag ("Exif");
    if (!exif) {
        exif = new Tag (root, root->getAttrib ("Exif"));
        exif->initSubDir();
        root->addTagFront (exif);
    }
    if (!exif->getDirectory()->getTag("ISOSpeedRatings")) {
        Tag *t = new Tag (exif->getDirectory(), exif->getDirectory()->getAttrib ("ISOSpeedRatings"));
        t->initInt (iso_speed, LONG);
        exif->getDirectory()->addTagFront (t);
    }
    if (!root->getTag("Orientation")) {
        int orientation;
        switch (rotation_angle) {
        case 0: orientation = 1; break;
        case 90: orientation = 6; break;
        case 180: orientation = 3; break;
        case 270: orientation = 8; break;
        default: orientation = 1; break;
        }
        Tag *t = new Tag (root, root->getAttrib ("Orientation"));
        t->initInt (orientation, SHORT);
        root->addTagFront (t);
    }

    // now look in ApplicationNotes tag for additional information
    Tag *appnotes = root->getTag("ApplicationNotes");
    if (!appnotes) {
        return;
    }
    char *xmp = (char *)appnotes->getValue();
    char *end, *p;
    // Quick-and-dirty value extractor, no real xml parsing.
    // We could make it more generic, but we just get most important
    // values we know use to be in there.
    if ((p = strstr(xmp, "xmlns:tiff")) != NULL &&
        (end = strstr(p, "</rdf:Description>")) != NULL)
    {
        *end = '\0';
        while ((p = strstr(p, "<tiff:")) != NULL) {
            char *tag = &p[6], *tagend;
            if ((tagend = strchr(tag, '>')) == NULL) {
                break;
            }
            *tagend = '\0';
            char *val = &tagend[1];
            if ((p = strstr(val, "</tiff:")) == NULL) {
                *tagend = '>';
                break;
            }
            *p = '\0';
            if (root->getAttrib (tag) && !root->getTag (tag)) {
                Tag *t = new Tag (root, root->getAttrib (tag));
                if (strcmp(tag, "Make") == 0 ||
                    strcmp(tag, "Model") == 0)
                {
                    if (strcmp(tag, "Model") == 0) {
                        // Leaf adds back serial number and camera model to the 'Model'
                        // tag, we strip that away here so the back can be recognized
                        // and matched against DCP profile
                        char *p1 = strchr(val, '(');
                        if (p1 != NULL) {
                            *p1 = '\0';
                        }
                        // Model name also contains a leading "Leaf " which we already
                        // have in the Make name, remove that.
                        if (strstr(val, "Leaf ") == val) {
                            t->initString (&val[5]);
                        } else {
                            t->initString (val);
                        }
                        if (p1 != NULL) {
                            *p1 = '(';
                        }
                    } else {
                        t->initString (val);
                    }
                    root->addTagFront (t);
                } else {
                    delete t;
                }
            }
            *p = '<';
            *tagend = '>';
        }
        *end = '<';
    }
    if ((p = strstr(xmp, "xmlns:exif")) != NULL &&
        (end = strstr(p, "</rdf:Description>")) != NULL)
    {
        *end = '\0';
        while ((p = strstr(p, "<exif:")) != NULL) {
            char *tag = &p[6], *tagend;
            if ((tagend = strchr(tag, '>')) == NULL) {
                break;
            }
            *tagend = '\0';
            char *val = &tagend[1];
            if ((p = strstr(val, "</exif:")) == NULL) {
                *tagend = '>';
                break;
            }
            *p = '\0';
            if (exif->getDirectory()->getAttrib (tag) && !exif->getDirectory()->getTag (tag)) {
                Tag *t = new Tag (exif->getDirectory(), exif->getDirectory()->getAttrib (tag));
                int num, denom;
                struct tm tm;
                if (strcmp(tag, "ApertureValue") == 0 && sscanf(val, "%d/%d", &num, &denom) == 2) {
                    t->initRational (num, denom);
                    exif->getDirectory()->addTagFront (t);
                    // we also make an "FNumber" tag since many tools don't interpret ApertureValue
                    // according to Exif standard
                    t = new Tag (exif->getDirectory(), lookupAttrib(exifAttribs,"FNumber"));
                    double f = pow(sqrt(2.0), ((double)num / denom));
                    if (f > 10.0) {
                        t->initRational ((int)floor(f), 1);
                    } else {
                        t->initRational ((int)floor(f * 10.0), 10);
                    }
                    exif->getDirectory()->addTagFront (t);
                } else if (strcmp(tag, "ShutterSpeedValue") == 0 && sscanf(val, "%d/%d", &num, &denom) == 2) {
                    t->initRational (num, denom);
                    exif->getDirectory()->addTagFront (t);
                    // we also make an "ExposureTime" tag since many tools don't interpret ShutterSpeedValue
                    // according to Exif standard
                    t = new Tag (exif->getDirectory(), lookupAttrib(exifAttribs,"ExposureTime"));
                    double f = 1.0 / pow(2.0, ((double)num / denom));
                    if (f > 10.0) {
                        t->initRational ((int)floor(f), 1);
                    } else if (f > 1.0) {
                        t->initRational ((int)floor(f * 10.0), 10);
                    } else if (f == 1.0) {
                        t->initRational (1, 1);
                    } else {
                        f = 1.0 / f;
                        static const double etimes[] = {
                            10000, 8000, 6400, 6000, 5000,
                            4000, 3200, 3000, 2500,
                            2000, 1600, 1500, 1250,
                            1000, 800, 750, 640,
                            500, 400, 350, 320,
                            250, 200, 180, 160,
                            125, 100, 90, 80,
                            60, 50, 45, 40,
                            30, 25, 22, 20,
                            15, 13, 11, 10,
                            8, 6, 5,
                            4, 3, 2.5,
                            2, 1.6, 1.5, 1.3,
                            1, -1 };
                        double diff = etimes[0];
                        int idx = -1;
                        for (int i = 1; etimes[i] > 0; i++) {
                            if (abs(etimes[i] - f) < diff) {
                                idx = i;
                                diff = abs(etimes[i] - f);
                            }
                        }
                        if (idx != -1 && f < etimes[0]) {
                            f = etimes[idx];
                        }
                        if (f < 2) {
                            t->initRational (10, (int)(10 * f));
                        } else {
                            t->initRational (1, (int)f);
                        }
                    }
                    exif->getDirectory()->addTagFront (t);
                } else if (strcmp(tag, "FocalLength") == 0 && sscanf(val, "%d/%d", &num, &denom) == 2) {
                    t->initRational (num, denom);
                    exif->getDirectory()->addTagFront (t);
                } else if (strcmp(tag, "ISOSpeedRatings") == 0) {
                    char *p1 = val;
                    while (*p1 != '\0' && !isdigit(*p1)) p1++;
                    if (*p1 != '\0') {
                        t->initInt (atoi(p1), LONG);
                        exif->getDirectory()->addTagFront (t);
                    }
                } else if (strcmp(tag, "DateTimeOriginal") == 0 &&
                           sscanf(val, "%d-%d-%dT%d:%d:%dZ",
                                  &tm.tm_year, &tm.tm_mon,
                                  &tm.tm_mday, &tm.tm_hour,
                                  &tm.tm_min, &tm.tm_sec) == 6)
                {
                    char tstr[64];
                    sprintf(tstr, "%04d:%02d:%02d %02d:%02d:%02d", tm.tm_year, tm.tm_mon,
                            tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
                    t->initString (tstr);
                    exif->getDirectory()->addTagFront (t);
                } else {
                    delete t;
                }
            }
            *p = '<';
            *tagend = '>';
        }
        *end = '<';
    }
}

TagDirectory* ExifManager::parse (FILE* f, int base, bool skipIgnored) {
  setlocale(LC_NUMERIC, "C"); // to set decimal point in sscanf
  // read tiff header
  fseek (f, base, SEEK_SET);
  unsigned short bo; 
  fread (&bo, 1, 2, f);
  ByteOrder order = (ByteOrder)((int)bo);
  get2 (f, order);
  int firstifd = get4 (f, order);

  // seek to IFD0
  fseek (f, base+firstifd, SEEK_SET);

  // first read the IFD directory
  TagDirectory* root =  new TagDirectory (NULL, f, base, ifdAttribs, order, skipIgnored);

  // fix ISO issue with nikon and panasonic cameras
  Tag* make = root->getTag ("Make");
  Tag* exif = root->getTag ("Exif");
  if (exif && !exif->getDirectory()->getTag("ISOSpeedRatings")) {
    if (make && !strncmp((char*)make->getValue(), "NIKON", 5)) {
        Tag* mn   = exif->getDirectory()->getTag("MakerNote");
        if (mn) {
            Tag* iso = mn->getDirectory()->getTag("ISOSpeed");
            if (iso) {
                std::string isov = iso->valueToString ();
                Tag* niso = new Tag (exif->getDirectory(), exif->getDirectory()->getAttrib ("ISOSpeedRatings"));
                niso->initInt (atoi(isov.c_str()), SHORT);
                exif->getDirectory()->addTagFront (niso);
            }
        }
    }
    else if (make && (!strncmp((char*)make->getValue(), "Panasonic", 9) || !strncmp((char*)make->getValue(), "LEICA", 5))) {
        Tag* iso = root->getTag("PanaISO");
        if (iso) {
            std::string isov = iso->valueToString ();
            Tag* niso = new Tag (exif->getDirectory(), exif->getDirectory()->getAttrib ("ISOSpeedRatings"));
            niso->initInt (atoi(isov.c_str()), SHORT);
            exif->getDirectory()->addTagFront (niso);
        }
    }
  }
  if (make && !strncmp((char*)make->getValue(), "Kodak", 5)) {
	  if (!exif) {
		  // old Kodak cameras may have exif tags in IFD0, reparse and create an exif subdir
		  fseek (f, base+firstifd, SEEK_SET);
		  TagDirectory* exifdir =  new TagDirectory (NULL, f, base, exifAttribs, order, true);

		  exif = new Tag (root, root->getAttrib ("Exif"));
		  exif->initSubDir(exifdir);
		  root->addTagFront (exif);

		  if (!exif->getDirectory()->getTag("ISOSpeedRatings") && exif->getDirectory()->getTag ("ExposureIndex")) {
			  Tag* niso = new Tag (exif->getDirectory(), exif->getDirectory()->getAttrib ("ISOSpeedRatings"));
			  niso->initInt (exif->getDirectory()->getTag ("ExposureIndex")->toInt(), SHORT);
			  exif->getDirectory()->addTagFront (niso);
		  }
	  }
	  Tag *kodakIFD = root->getTag("KodakIFD");
	  if (kodakIFD && kodakIFD->getDirectory()->getTag("TextualInfo")) {
		  parseKodakIfdTextualInfo(kodakIFD->getDirectory()->getTag("TextualInfo"), exif);
	  }
  }

  parse_leafdata(root, order);

  if (!root->getTag("Orientation")) {
      if (make && !strncmp((char*)make->getValue(), "Phase One", 9)) {
          int orientation = 0;
          Tag *iw = root->getTag("ImageWidth");
          if (iw) {
              // from dcraw, derive orientation from image width
              orientation = "0653"[iw->toInt()&3]-'0';
          }
          Tag *t = new Tag (root, root->getAttrib ("Orientation"));
          t->initInt (orientation, SHORT);
          root->addTagFront (t);
      }
  }

// root->printAll ();

  return root;
}

TagDirectory* ExifManager::parseJPEG (FILE* f) {

  fseek (f, 0, SEEK_SET);
  unsigned char markerl = 0xff;
  unsigned char c;
  fread (&c, 1, 1, f);
  const char exifid[] = "Exif\0\0";
  char idbuff[8];
  int tiffbase = -1;
  while (fread (&c, 1, 1, f)) {
    if (c!=markerl) continue;
    if (fread (&c, 1, 1, f) && c==0xe1) {  // APP1 marker found
      if (fread (idbuff, 1, 8, f)<8)
        return NULL;
      if (!memcmp(idbuff+2, exifid, 6)) {     // Exif info found
        tiffbase = ftell (f);        
        return parse (f, tiffbase);
      }
    }
  }
  return NULL;
}

TagDirectory* ExifManager::parseTIFF (FILE* f, bool skipIgnored) {

  return parse (f, 0, skipIgnored);
}

std::vector<Tag*> ExifManager::defTags; 

// forthis: the byte order will be taken from directory "forthis"
const std::vector<Tag*>& ExifManager::getDefaultTIFFTags (TagDirectory* forthis) {

    for (size_t i=0; i<defTags.size(); i++)
        delete defTags[i];
    defTags.clear ();
    defTags.push_back (new Tag (forthis, lookupAttrib(ifdAttribs,"ImageWidth"), 0, LONG));
    defTags.push_back (new Tag (forthis, lookupAttrib(ifdAttribs,"ImageHeight"), 0, LONG));
    defTags.push_back (new Tag (forthis, lookupAttrib(ifdAttribs,"XResolution"), 300, RATIONAL));
    defTags.push_back (new Tag (forthis, lookupAttrib(ifdAttribs,"YResolution"), 300, RATIONAL));
    defTags.push_back (new Tag (forthis, lookupAttrib(ifdAttribs,"ResolutionUnit"), 2, SHORT));
    defTags.push_back (new Tag (forthis, lookupAttrib(ifdAttribs,"Software"), "RawTherapee"));
    defTags.push_back (new Tag (forthis, lookupAttrib(ifdAttribs,"Orientation"), 1, SHORT));
    defTags.push_back (new Tag (forthis, lookupAttrib(ifdAttribs,"SamplesPerPixel"), 3, SHORT));
    defTags.push_back (new Tag (forthis, lookupAttrib(ifdAttribs,"BitsPerSample"), 8, SHORT));
    defTags.push_back (new Tag (forthis, lookupAttrib(ifdAttribs,"PlanarConfiguration"), 1, SHORT));
    defTags.push_back (new Tag (forthis, lookupAttrib(ifdAttribs,"PhotometricInterpretation"), 2, SHORT));
    defTags.push_back (new Tag (forthis, lookupAttrib(ifdAttribs,"Compression"), 1, SHORT));
    
    return defTags;
}



int ExifManager::createJPEGMarker (const TagDirectory* root, const rtengine::procparams::ExifPairs& changeList, int W, int H, unsigned char* buffer) {

  // write tiff header
  int offs = 6;
  memcpy (buffer, "Exif\0\0", 6);
  ByteOrder order = INTEL;
  if (root)
    order = root->getOrder ();
  sset2 ((unsigned short)order, buffer+offs, order); offs += 2;
  sset2 (42, buffer+offs, order); offs += 2;
  sset4 (8, buffer+offs, order); offs += 4;

  TagDirectory* cl;
  if (root)
    cl = (const_cast<TagDirectory*>(root))->clone (NULL);
  else
    cl = new TagDirectory (NULL, ifdAttribs, INTEL);

  for (rtengine::procparams::ExifPairs::const_iterator i=changeList.begin(); i!=changeList.end(); i++)
     cl->applyChange (i->first, i->second);
  
   getDefaultTIFFTags (cl);
  
  defTags[0]->setInt (W, 0, LONG);
  defTags[1]->setInt (H, 0, LONG);
  defTags[8]->setInt (8, 0, SHORT);
  
  for (int i=defTags.size()-1; i>=0; i--) 
    cl->replaceTag (defTags[i]->clone (cl));
  cl->sort ();
  int size = cl->write (8, buffer+6);

  delete cl;
  
  return size + 6;
}

int ExifManager::createTIFFHeader (const TagDirectory* root, const rtengine::procparams::ExifPairs& changeList, int W, int H, int bps, const char* profiledata, int profilelen, const char* iptcdata, int iptclen, unsigned char* buffer) {

// write tiff header
    int offs = 0;
    ByteOrder order = INTEL;
    if (root)
        order = root->getOrder ();
    sset2 ((unsigned short)order, buffer+offs, order); offs += 2;
    sset2 (42, buffer+offs, order); offs += 2;
    sset4 (8, buffer+offs, order); offs += 4;

    TagDirectory* cl;
    if (root)
	cl = (const_cast<TagDirectory*>(root))->clone (NULL);
    else
        cl = new TagDirectory (NULL, ifdAttribs, INTEL);

// add tiff strip data
    int rps = 8;
    int strips = ceil((double)H/rps);
    cl->replaceTag (new Tag (cl, lookupAttrib(ifdAttribs,"RowsPerStrip"), rps, LONG));
    Tag* stripBC   = new Tag (cl, lookupAttrib(ifdAttribs,"StripByteCounts"));
    stripBC->initInt (0, LONG, strips);
    cl->replaceTag (stripBC);
    Tag* stripOffs = new Tag (cl, lookupAttrib(ifdAttribs,"StripOffsets"));
    stripOffs->initInt (0, LONG, strips);
    cl->replaceTag (stripOffs);
    for (int i=0; i<strips-1; i++)
        stripBC->setInt (rps*W*3*bps/8, i*4);
    int remaining = (H-rps*floor((double)H/rps))*W*3*bps/8;
    if (remaining)
        stripBC->setInt (remaining, (strips-1)*4);   
    else
        stripBC->setInt (rps*W*3*bps/8, (strips-1)*4);
    if (profiledata) {
        Tag* icc = new Tag (cl, lookupAttrib(ifdAttribs,"ICCProfile"));
        icc->initUndefArray (profiledata, profilelen);
        cl->replaceTag (icc);
    }
    if (iptcdata) {
        Tag* iptc = new Tag (cl, lookupAttrib(ifdAttribs,"IPTCData"));
        iptc->initLongArray (iptcdata, iptclen);
        cl->replaceTag (iptc);
    }
    
// apply list of changes
    for (rtengine::procparams::ExifPairs::const_iterator i=changeList.begin(); i!=changeList.end(); i++)
        cl->applyChange (i->first, i->second);
  
  // append default properties   
        getDefaultTIFFTags (cl);

    defTags[0]->setInt (W, 0, LONG);
    defTags[1]->setInt (H, 0, LONG);
    defTags[8]->initInt(0, SHORT, 3);
    for (int i=0;i<3;i++) defTags[8]->setInt(bps, i*2, SHORT);
 
    for (int i=defTags.size()-1; i>=0; i--) 
        cl->replaceTag (defTags[i]->clone (cl));
   
// calculate strip offsets  
    int size = cl->calculateSize ();
    int byps = bps / 8;
    for (int i=0; i<strips; i++)
        stripOffs->setInt (size + 8 + i*rps*W*3*byps, i*4);

    cl->sort ();
    int endOffs = cl->write (8, buffer);
  
//  cl->printAll();
    delete cl;

    return endOffs;
}

//-----------------------------------------------------------------------------
// global functions to read byteorder dependent data
//-----------------------------------------------------------------------------
unsigned short sget2 (unsigned char *s, rtexif::ByteOrder order) {

  if (order == rtexif::INTEL)   return s[0] | s[1] << 8;
  else			return s[0] << 8 | s[1];
}

int sget4 (unsigned char *s, rtexif::ByteOrder order) {

  if (order == rtexif::INTEL)   return s[0] | s[1] << 8 | s[2] << 16 | s[3] << 24;
  else                  return s[0] << 24 | s[1] << 16 | s[2] << 8 | s[3];
}

inline unsigned short get2 (FILE* f, rtexif::ByteOrder order) {

  unsigned char str[2] = { 0xff,0xff };
  fread (str, 1, 2, f);
  return rtexif::sget2 (str, order);
}

int get4 (FILE* f, rtexif::ByteOrder order) {

  unsigned char str[4] = { 0xff,0xff,0xff,0xff };
  fread (str, 1, 4, f);
  return rtexif::sget4 (str, order);
}

void sset2 (unsigned short v, unsigned char *s, rtexif::ByteOrder order) {

  if (order == rtexif::INTEL) {
    s[0] = v & 0xff; v >>= 8;
    s[1] = v;
  }
  else {
    s[1] = v & 0xff; v >>= 8;
    s[0] = v;
  }
}

void sset4 (int v, unsigned char *s, rtexif::ByteOrder order) {

  if (order == rtexif::INTEL) {
    s[0] = v & 0xff; v >>= 8;
    s[1] = v & 0xff; v >>= 8;
    s[2] = v & 0xff; v >>= 8;
    s[3] = v;
  }
  else {
    s[3] = v & 0xff; v >>= 8;
    s[2] = v & 0xff; v >>= 8;
    s[1] = v & 0xff; v >>= 8;
    s[0] = v;
  }
}

float int_to_float (int i) {
  union { int i; float f; } u;
  u.i = i;
  return u.f;
}

short int int2_to_signed (short unsigned int i) {
  union { short unsigned int i; short int s; } u;
  u.i = i;
  return u.s;
}

int getTypeSize( TagType type ){
	return ("11124811248484"[type<14?type:0]-'0');
}

/* Function to parse and extract focal length and aperture information from description
 * @fullname must conform to the following formats
 * <focal>mm f/<aperture>
 * <focal>-<focal>mm f/<aperture>
 * <focal>-<focal>mm f/<aperture>-<aperture>
 * NB: no space between separator '-'; no space between focal length and 'mm'
 */
bool extractLensInfo(std::string &fullname,double &minFocal, double &maxFocal, double &maxApertureAtMinFocal, double &maxApertureAtMaxFocal)
{
	 minFocal=0.0;
	 maxFocal=0.0;
	 maxApertureAtMinFocal=0.0;
	 maxApertureAtMaxFocal=0.0;
	 char buffer[1024];
	 strcpy(buffer,fullname.c_str());
	 char *pF = strstr(buffer,"f/" );
	 if( pF ){
		 sscanf(pF+2,"%lf-%lf",&maxApertureAtMinFocal,&maxApertureAtMaxFocal);
		 if(maxApertureAtMinFocal >0. && maxApertureAtMaxFocal==0.)
			 maxApertureAtMaxFocal= maxApertureAtMinFocal;

		 char *pMM = pF-3;
		 while( pMM[0]!= 'm' && pMM[1]!= 'm' && pMM>buffer) pMM--;
		 if( pMM[0]== 'm' && pMM[1]== 'm' ){
		    char *sp=pMM;
		    while( *sp != ' ' && sp > buffer )sp--;
		    sscanf(sp+1,"%lf-%lf",&minFocal,&maxFocal);
		    if(maxFocal==0.) {
		    	maxFocal = minFocal;
		    }
		    return true;
		 }
	 }
     return false;
}

}
