// Filename: imageFile.cxx
// Created by:  drose (29Nov00)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) Carnegie Mellon University.  All rights reserved.
//
// All use of this software is subject to the terms of the revised BSD
// license.  You should have received a copy of this license along
// with this source code in a file named "LICENSE."
//
////////////////////////////////////////////////////////////////////

#include "imageFile.h"
#include "palettizer.h"
#include "filenameUnifier.h"
#include "paletteGroup.h"

#include "pnmImage.h"
#include "pnmFileType.h"
#include "eggTexture.h"
#include "datagram.h"
#include "datagramIterator.h"
#include "bamReader.h"
#include "bamWriter.h"

TypeHandle ImageFile::_type_handle;

////////////////////////////////////////////////////////////////////
//     Function: ImageFile::Constructor
//       Access: Public
//  Description:
////////////////////////////////////////////////////////////////////
ImageFile::
ImageFile() {
  _alpha_file_channel = 0;
  _size_known = false;
  _x_size = 0;
  _y_size = 0;
}

////////////////////////////////////////////////////////////////////
//     Function: ImageFile::make_shadow_image
//       Access: Public
//  Description: Sets up the ImageFile as a "shadow image" of a
//               particular PaletteImage.  This is a temporary
//               ImageFile that's used to read and write the shadow
//               palette image, which is used to keep a working copy
//               of the palette.
//
//               Returns true if the filename changes from what it was
//               previously, false otherwise.
////////////////////////////////////////////////////////////////////
bool ImageFile::
make_shadow_image(const string &basename) {
  bool any_changed = false;
  
  if (_properties._color_type != pal->_shadow_color_type ||
      _properties._alpha_type != pal->_shadow_alpha_type) {

    _properties._color_type = pal->_shadow_color_type;
    _properties._alpha_type = pal->_shadow_alpha_type;
    any_changed = true;
  }

  if (set_filename(pal->_shadow_dirname, basename)) {
    any_changed = true;
  }

  return any_changed;
}

////////////////////////////////////////////////////////////////////
//     Function: ImageFile::is_size_known
//       Access: Public
//  Description: Returns true if the size of the image file is known,
//               false otherwise.
////////////////////////////////////////////////////////////////////
bool ImageFile::
is_size_known() const {
  return _size_known;
}

////////////////////////////////////////////////////////////////////
//     Function: ImageFile::get_x_size
//       Access: Public
//  Description: Returns the size of the image file in pixels in the X
//               direction.  It is an error to call this unless
//               is_size_known() returns true.
////////////////////////////////////////////////////////////////////
int ImageFile::
get_x_size() const {
  nassertr(is_size_known(), 0);
  return _x_size;
}

////////////////////////////////////////////////////////////////////
//     Function: ImageFile::get_y_size
//       Access: Public
//  Description: Returns the size of the image file in pixels in the Y
//               direction.  It is an error to call this unless
//               is_size_known() returns true.
////////////////////////////////////////////////////////////////////
int ImageFile::
get_y_size() const {
  nassertr(is_size_known(), 0);
  return _y_size;
}

////////////////////////////////////////////////////////////////////
//     Function: ImageFile::has_num_channels
//       Access: Public
//  Description: Returns true if the number of channels in the image
//               is known, false otherwise.
////////////////////////////////////////////////////////////////////
bool ImageFile::
has_num_channels() const {
  return _properties.has_num_channels();
}

////////////////////////////////////////////////////////////////////
//     Function: ImageFile::get_num_channels
//       Access: Public
//  Description: Returns the number of channels of the image.  It is
//               an error to call this unless has_num_channels()
//               returns true.
////////////////////////////////////////////////////////////////////
int ImageFile::
get_num_channels() const {
  return _properties.get_num_channels();
}

////////////////////////////////////////////////////////////////////
//     Function: ImageFile::get_properties
//       Access: Public
//  Description: Returns the grouping properties of the image.
////////////////////////////////////////////////////////////////////
const TextureProperties &ImageFile::
get_properties() const {
  return _properties;
}

////////////////////////////////////////////////////////////////////
//     Function: ImageFile::clear_basic_properties
//       Access: Public
//  Description: Resets the properties to a neutral state, for
//               instance in preparation for calling
//               update_properties() with all the known contributing
//               properties.
////////////////////////////////////////////////////////////////////
void ImageFile::
clear_basic_properties() {
  _properties.clear_basic();
}

////////////////////////////////////////////////////////////////////
//     Function: ImageFile::update_properties
//       Access: Public
//  Description: If the indicate TextureProperties structure is more
//               specific than this one, updates this one.
////////////////////////////////////////////////////////////////////
void ImageFile::
update_properties(const TextureProperties &properties) {
  _properties.update_properties(properties);
}

////////////////////////////////////////////////////////////////////
//     Function: ImageFile::set_filename
//       Access: Public
//  Description: Sets the filename, and if applicable, the
//               alpha_filename, from the indicated basename.  The
//               extension appropriate to the image file type
//               specified in _color_type (and _alpha_type) is
//               automatically applied.
//
//               Returns true if the filename changes from what it was
//               previously, false otherwise.
////////////////////////////////////////////////////////////////////
bool ImageFile::
set_filename(PaletteGroup *group, const string &basename) {
  // Synthesize the directory name based on the map_dirname set to the
  // palettizer, and the group's dirname.
  string dirname;
  string::iterator pi;
  pi = pal->_map_dirname.begin();
  while (pi != pal->_map_dirname.end()) {
    if (*pi == '%') {
      ++pi;
      switch (*pi) {
      case '%':
        dirname += '%';
        break;

      case 'g':
        if (group != (PaletteGroup *)NULL) {
          dirname += group->get_dirname();
        }
        break;
      }
    } else {
      dirname += *pi;
    }
    ++pi;
  }

  return set_filename(dirname, basename);
}

////////////////////////////////////////////////////////////////////
//     Function: ImageFile::set_filename
//       Access: Public
//  Description: Sets the filename, and if applicable, the
//               alpha_filename, from the indicated basename.  The
//               extension appropriate to the image file type
//               specified in _color_type (and _alpha_type) is
//               automatically applied.
//
//               Returns true if the filename changes from what it was
//               previously, false otherwise.
////////////////////////////////////////////////////////////////////
bool ImageFile::
set_filename(const string &dirname, const string &basename) {
  Filename orig_filename = _filename;
  Filename orig_alpha_filename = _alpha_filename;
  
  _filename = Filename(dirname, basename);
  _filename.standardize();

  // Since we use set_extension() here, if the file already contains a
  // filename extension it will be lost.

  // It is particularly important to note that a single embedded dot
  // will appear to begin a filename extension, so if the filename
  // does *not* contain an extension, but does contain an embedded
  // dot, the filename will be truncated at that dot.  It is therefore
  // important that the supplied basename always contains either an
  // extension or a terminating dot.

  if (_properties._color_type != (PNMFileType *)NULL) {
    _filename.set_extension
      (_properties._color_type->get_suggested_extension());
  }

  if (_properties._alpha_type != (PNMFileType *)NULL) {
    _alpha_filename = _filename.get_fullpath_wo_extension() + "_a.";
    _alpha_filename.set_extension
      (_properties._alpha_type->get_suggested_extension());
  } else {
    _alpha_filename = Filename();
  }

  return (_filename != orig_filename ||
          _alpha_filename != orig_alpha_filename);
}

////////////////////////////////////////////////////////////////////
//     Function: ImageFile::get_filename
//       Access: Public
//  Description: Returns the primary filename of the image file.
////////////////////////////////////////////////////////////////////
const Filename &ImageFile::
get_filename() const {
  return _filename;
}

////////////////////////////////////////////////////////////////////
//     Function: ImageFile::get_alpha_filename
//       Access: Public
//  Description: Returns the alpha filename of the image file.  This
//               is the name of the file that contains the alpha
//               channel, if it is stored in a separate file, or the
//               empty string if it is not.
////////////////////////////////////////////////////////////////////
const Filename &ImageFile::
get_alpha_filename() const {
  return _alpha_filename;
}

////////////////////////////////////////////////////////////////////
//     Function: ImageFile::get_alpha_file_channel
//       Access: Public
//  Description: Returns the particular channel number of the alpha
//               image file from which the alpha channel should be
//               extracted.  This is normally 0 to represent the
//               grayscale combination of r, g, and b; or it may be a
//               1-based channel number (for instance, 4 for the alpha
//               channel of a 4-component image).
////////////////////////////////////////////////////////////////////
int ImageFile::
get_alpha_file_channel() const {
  return _alpha_file_channel;
}


////////////////////////////////////////////////////////////////////
//     Function: ImageFile::exists
//       Access: Public
//  Description: Returns true if the file or files named by the image
//               file exist, false otherwise.
////////////////////////////////////////////////////////////////////
bool ImageFile::
exists() const {
  if (!_filename.exists()) {
    return false;
  }
  if (_properties._alpha_type != (PNMFileType *)NULL &&
      _properties.uses_alpha() &&
      !_alpha_filename.empty()) {
    if (!_alpha_filename.exists()) {
      return false;
    }
  }

  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: ImageFile::read
//       Access: Public
//  Description: Reads in the image (or images, if the alpha_filename
//               is separate) and stores it in the indicated PNMImage.
//               Returns true on success, false on failure.
////////////////////////////////////////////////////////////////////
bool ImageFile::
read(PNMImage &image) const {
  nassertr(!_filename.empty(), false);

  image.set_type(_properties._color_type);
  nout << "Reading " << FilenameUnifier::make_user_filename(_filename) << "\n";
  if (!image.read(_filename)) {
    nout << "Unable to read.\n";
    return false;
  }

  if (!_alpha_filename.empty() && _alpha_filename.exists()) {
    // Read in a separate color image and an alpha channel image.
    PNMImage alpha_image;
    alpha_image.set_type(_properties._alpha_type);
    nout << "Reading " << FilenameUnifier::make_user_filename(_alpha_filename) << "\n";
    if (!alpha_image.read(_alpha_filename)) {
      nout << "Unable to read.\n";
      return false;
    }
    if (image.get_x_size() != alpha_image.get_x_size() ||
        image.get_y_size() != alpha_image.get_y_size()) {
      return false;
    }

    image.add_alpha();

    if (_alpha_file_channel == 4 || 
        (_alpha_file_channel == 2 && alpha_image.get_num_channels() == 2)) {
      // Use the alpha channel.
      for (int x = 0; x < image.get_x_size(); x++) {
        for (int y = 0; y < image.get_y_size(); y++) {
          image.set_alpha(x, y, alpha_image.get_alpha(x, y));
        }
      }
      
    } else if (_alpha_file_channel >= 1 && _alpha_file_channel <= 3 &&
               alpha_image.get_num_channels() >= 3) {
      // Use the appropriate red, green, or blue channel.
      for (int x = 0; x < image.get_x_size(); x++) {
        for (int y = 0; y < image.get_y_size(); y++) {
          image.set_alpha(x, y, alpha_image.get_channel_val(x, y, _alpha_file_channel - 1));
        }
      }
      
    } else {
      // Use the grayscale channel.
      for (int x = 0; x < image.get_x_size(); x++) {
        for (int y = 0; y < image.get_y_size(); y++) {
          image.set_alpha(x, y, alpha_image.get_gray(x, y));
        }
      }
    }
  }

  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: ImageFile::write
//       Access: Public
//  Description: Writes out the image in the indicated PNMImage to the
//               _filename and/or _alpha_filename.  Returns true on
//               success, false on failure.
////////////////////////////////////////////////////////////////////
bool ImageFile::
write(const PNMImage &image) const {
  nassertr(!_filename.empty(), false);

  if (!image.has_alpha() ||
      _properties._alpha_type == (PNMFileType *)NULL) {
    if (!_alpha_filename.empty() && _alpha_filename.exists()) {
      nout << "Deleting " << FilenameUnifier::make_user_filename(_alpha_filename) << "\n";
      _alpha_filename.unlink();
    }
    nout << "Writing " << FilenameUnifier::make_user_filename(_filename) << "\n";
    _filename.make_dir();
    if (!image.write(_filename, _properties._color_type)) {
      nout << "Unable to write.\n";
      return false;
    }
    return true;
  }

  // Write out a separate color image and an alpha channel image.
  PNMImage alpha_image(image.get_x_size(), image.get_y_size(), 1,
                       image.get_maxval());
  for (int y = 0; y < image.get_y_size(); y++) {
    for (int x = 0; x < image.get_x_size(); x++) {
      alpha_image.set_gray_val(x, y, image.get_alpha_val(x, y));
    }
  }

  PNMImage image_copy(image);
  image_copy.remove_alpha();
  nout << "Writing " << FilenameUnifier::make_user_filename(_filename) << "\n";
  _filename.make_dir();
  if (!image_copy.write(_filename, _properties._color_type)) {
    nout << "Unable to write.\n";
    return false;
  }

  nout << "Writing " << FilenameUnifier::make_user_filename(_alpha_filename) << "\n";
  _alpha_filename.make_dir();
  if (!alpha_image.write(_alpha_filename, _properties._alpha_type)) {
    nout << "Unable to write.\n";
    return false;
  }
  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: ImageFile::unlink
//       Access: Public
//  Description: Deletes the image file or files.
////////////////////////////////////////////////////////////////////
void ImageFile::
unlink() {
  if (!_filename.empty() && _filename.exists()) {
    nout << "Deleting " << FilenameUnifier::make_user_filename(_filename) << "\n";
    _filename.unlink();
  }
  if (!_alpha_filename.empty() && _alpha_filename.exists()) {
    nout << "Deleting " << FilenameUnifier::make_user_filename(_alpha_filename) << "\n";
    _alpha_filename.unlink();
  }
}

////////////////////////////////////////////////////////////////////
//     Function: ImageFile::update_egg_tex
//       Access: Public
//  Description: Sets the indicated EggTexture to refer to this file.
////////////////////////////////////////////////////////////////////
void ImageFile::
update_egg_tex(EggTexture *egg_tex) const {
  nassertv(egg_tex != (EggTexture *)NULL);

  egg_tex->set_filename(FilenameUnifier::make_egg_filename(_filename));

  if (_properties.uses_alpha() &&
      !_alpha_filename.empty()) {
    egg_tex->set_alpha_filename(FilenameUnifier::make_egg_filename(_alpha_filename));
    egg_tex->set_alpha_file_channel(_alpha_file_channel);
  } else {
    egg_tex->clear_alpha_filename();
    egg_tex->clear_alpha_file_channel();
  }

  _properties.update_egg_tex(egg_tex);
}

////////////////////////////////////////////////////////////////////
//     Function: ImageFile::output_filename
//       Access: Public
//  Description: Writes the filename (or pair of filenames) to the
//               indicated output stream.
////////////////////////////////////////////////////////////////////
void ImageFile::
output_filename(ostream &out) const {
  out << FilenameUnifier::make_user_filename(_filename); 
  if (_properties.uses_alpha() && !_alpha_filename.empty()) {
    out << " " << FilenameUnifier::make_user_filename(_alpha_filename);
  }
}

////////////////////////////////////////////////////////////////////
//     Function: ImageFile::write_datagram
//       Access: Public, Virtual
//  Description: Fills the indicated datagram up with a binary
//               representation of the current object, in preparation
//               for writing to a Bam file.
////////////////////////////////////////////////////////////////////
void ImageFile::
write_datagram(BamWriter *writer, Datagram &datagram) {
  TypedWritable::write_datagram(writer, datagram);
  _properties.write_datagram(writer, datagram);
  datagram.add_string(FilenameUnifier::make_bam_filename(_filename));
  datagram.add_string(FilenameUnifier::make_bam_filename(_alpha_filename));
  datagram.add_uint8(_alpha_file_channel);
  datagram.add_bool(_size_known);
  datagram.add_int32(_x_size);
  datagram.add_int32(_y_size);
}

////////////////////////////////////////////////////////////////////
//     Function: ImageFile::complete_pointers
//       Access: Public, Virtual
//  Description: Called after the object is otherwise completely read
//               from a Bam file, this function's job is to store the
//               pointers that were retrieved from the Bam file for
//               each pointer object written.  The return value is the
//               number of pointers processed from the list.
////////////////////////////////////////////////////////////////////
int ImageFile::
complete_pointers(TypedWritable **p_list, BamReader *manager) {
  int pi = TypedWritable::complete_pointers(p_list, manager);

  pi += _properties.complete_pointers(p_list + pi, manager);

  return pi;
}

////////////////////////////////////////////////////////////////////
//     Function: ImageFile::fillin
//       Access: Protected
//  Description: Reads the binary data from the given datagram
//               iterator, which was written by a previous call to
//               write_datagram().
////////////////////////////////////////////////////////////////////
void ImageFile::
fillin(DatagramIterator &scan, BamReader *manager) {
  TypedWritable::fillin(scan, manager);
  _properties.fillin(scan, manager);
  _filename = FilenameUnifier::get_bam_filename(scan.get_string());
  _alpha_filename = FilenameUnifier::get_bam_filename(scan.get_string());
  if (Palettizer::_read_pi_version >= 10) {
    _alpha_file_channel = scan.get_uint8();
  } else {
    _alpha_file_channel = 0;
  }
  _size_known = scan.get_bool();
  _x_size = scan.get_int32();
  _y_size = scan.get_int32();
}
