/*------------------------------------------------------------------------------
 src/bin/djop/djop.cxx

 Copyright (c) 2004-2006 Darby Johnston.
 All rights reserved.
 Use is subject to license terms.
------------------------------------------------------------------------------*/

//#define DJ_DBG
#include <djImageOp.h>
#include <djImageOpCore.h>
#include <djImageOpXform.h>
#include <djImageIo.h>
#include <djImageApp.h>

namespace djop {
using namespace dj;

class App : public ImageApp {
public:

  App(int &, char **);
  ~App();

private:

  bool work();
  Str cmdln(StrList &);
  void help();

  Str _in, _out;
  Box2i _cropArea;
  ImageOpMirror::MIRROR _mirror;
  ImageOpRotate90::ROTATE _rotate90;
  Vec2i _scaleSize;
  ImageOpScale::FILTER _scaleFilterX, _scaleFilterY;
  Image::EDGE _scaleEdgeX, _scaleEdgeY;
  Str _load, _save;
  Map<Str, PluginAttr> _loadAttr, _saveAttr;
  PIXEL_FORMAT * _loadFormat, * _saveFormat;
  PIXEL_TYPE * _loadType, * _saveType;
  int _proxy;
  int * _time;
  double * _speed;
  bool _silent;
};

App::App(int & argc, char ** argv) :
  ImageApp("djop", argc, argv),
  _mirror(ImageOpMirror::MIRROR(0)),
  _rotate90(ImageOpRotate90::ROTATE(0)),
  _scaleFilterX(ImageOpScale::AUTO), _scaleFilterY(ImageOpScale::AUTO),
    _scaleEdgeX(Image::EDGE_MIRROR), _scaleEdgeY(Image::EDGE_MIRROR),
  _loadFormat(0), _saveFormat(0), _loadType(0), _saveType(0),
  _proxy(0), _time(0), _speed(0),
  _silent(false)
{
  DJ_DBG_FNC("djop::App::App");
  
  // Command line.
  //
  if (1 == argc) { help(); return; }
  StrList argList;
  for (int i = 1; i < argc; ++i) {
    const Str it = argv[i];
    if (Str("-help") == it || Str("-h") == it) { help(); return; }
    else if (Str("-about") == it) { about(); return; }
    else argList += it;
  }
  const Str err = cmdln(argList);
  if (! err.isNull()) {
    error(Str("Error parsing command line \"%1\".").arg(err));
    exitValue(1);
    return;
  }
  
  // Set image I/O attributes.
  //
  StrList keyList = _loadAttr.keyList();
  for (StrListIt it = keyList; it.isValid(); ++it) {
    const StrList attr = _loadAttr[it()].keyList();
    for (ConstStrListIt name = attr; name.isValid(); ++name)  
      if (! imageLoadAttr(it(), name(), _loadAttr[it()].get(name()))) {
        error(Str("Error parsing image load attribute \"%1\".").arg(name()));
        exitValue(1);
        return;
      }
  }
  keyList = _saveAttr.keyList();
  for (StrListIt it = keyList; it.isValid(); ++it) {
    const StrList attr = _saveAttr[it()].keyList();
    for (ConstStrListIt name = attr; name.isValid(); ++name)  
      if (! imageSaveAttr(it(), name(), _saveAttr[it()].get(name()))) {
        error(Str("Error parsing image save attribute \"%1\".").arg(name()));
        exitValue(1);
        return;
      }
  }
  
  // Work.
  //
  exitValue(! work());
}

App::~App()
{
  if (_loadFormat) delete _loadFormat;
  if (_saveFormat) delete _saveFormat;
  if (_loadType) delete _loadType;
  if (_saveType) delete _saveType;
  if (_time) delete _time;
  if (_speed) delete _speed;
}

static const Str toStr(const ImageIoInfo & in)
{
  return Str("%1x%2:%3 %4 %5 %6-%7:%8").
    arg(in.size().x()).arg(in.size().y()).arg(aspect(in.size()), 0, 2).
    arg(pixelFormatStr()[in.format()]).arg(pixelTypeStr()[in.type()]).
    arg(in.timeStart()).arg(in.timeEnd()).arg(in.timeSpeed(), 0, 2);
}

bool App::work()
{
  DJ_DBG_FNC("djop::App::work");
  DJ_DBG_PRN("input = " << _in);
  DJ_DBG_PRN("output = " << _out);
  DJ_DBG_PRN("load = " << _load);
  DJ_DBG_PRN("crop = " << _cropArea);
  DJ_DBG_PRN("mirror = " << imageOpMirrorStr()[_mirror]);
  DJ_DBG_PRN("rotate 90 = " << imageOpRotate90Str()[_rotate90]);
  DJ_DBG_PRN("scale = " << _scaleSize);
  if (_loadFormat) DJ_DBG_PRN("load format = " << *_loadFormat);
  if (_loadType) DJ_DBG_PRN("load type = " << *_loadType);
  DJ_DBG_PRN("save = " << _save);
  if (_saveFormat) DJ_DBG_PRN("save format = " << *_saveFormat);
  if (_saveType) DJ_DBG_PRN("save type = " << *_saveType);
  DJ_DBG_PRN("proxy = " << _proxy);
  if (_time) DJ_DBG_PRN("time = " << _time[0] << " " << _time[1]);
  if (_speed) DJ_DBG_PRN("speed = " << *_speed);

  bool r = false;
  ImageLoad * load = 0;
  ImageSave * save = 0;
do {

  // Timers.
  //
  Timer totalTimer, frameTimer;
  totalTimer.start();
  
  // Open input.
  //
  ImageIoInfo loadInfo;
  load = dj::imageLoad(_in, &loadInfo, _load);
  if (! load) {
    error(Str("Cannot open \"%1\".").arg(_in));
    break;
  }
  if (! _silent)
    print(Str("input = %1 %2").arg(_in).arg(toStr(loadInfo)));

  // Initialize operations.
  //
  PixelDataInfo opInfo(
    loadInfo.size() / imageProxyScale(_proxy), loadInfo.pixel()
  );
  if (areaIsValid(_cropArea)) opInfo = ImageOpCrop::info(opInfo, _cropArea);
  if (_rotate90) opInfo = ImageOpRotate90::info(opInfo, _rotate90);
  if (areaIsValid(_scaleSize)) opInfo = ImageOpScale::info(opInfo, _scaleSize);
  
  // Open output.
  //
  FileInfo fileInfo(_out);
  ImageIoInfo saveInfo;
  saveInfo.size(opInfo.size());
  saveInfo.pixel(
    Pixel(
      _saveFormat ? *_saveFormat : opInfo.pixel().format(),
      _saveType ? *_saveType : opInfo.pixel().type()
    )
  );
  saveInfo.time(
    _time ? _time[0] :
      (fileInfo.isSeq() ? fileInfo.seq().start() : loadInfo.timeStart()),
    _time ? _time[1] :
      (fileInfo.isSeq() ? fileInfo.seq().end() : loadInfo.timeEnd()),
    _speed ? *_speed : loadInfo.timeSpeed()
  );
  if (! _silent)
    print(Str("output = %1 %2").arg(_out).arg(toStr(saveInfo)));
  save = dj::imageSave(_out, saveInfo, _save);
  if (! save) {
    error(Str("Cannot open \"%1\".").arg(_out));
    break;
  }
  
  // Work.
  //
  Image image;
  const int length = saveInfo.timeEnd() - saveInfo.timeStart() + 1;
  int i = 0;
  for (; i < length; ++i) {
    frameTimer.start();
    
    // Load input.
    //
    int frame = loadInfo.timeStart() + i;
    load->get(
      &image,
      _proxy,
      _loadFormat ? *_loadFormat : -1,
      _loadType ? *_loadType : -1,
      frame
    );
    DJ_DBG_PRN("input = " << image);
    
    // Operations.
    //
    if (areaIsValid(_cropArea)) image = ImageOpCrop::work(image, _cropArea);
    if (_mirror) image = ImageOpMirror::work(image, _mirror);
    if (_rotate90) image = ImageOpRotate90::work(image, _rotate90);
    if (areaIsValid(_scaleSize))
      image = ImageOpScale::work(
        image,
        _scaleSize,
        _scaleFilterX, _scaleFilterY,
        _scaleEdgeX, _scaleEdgeY
      );
    DJ_DBG_PRN("output = " << image);

    // Save output.
    //
    frame = saveInfo.timeStart() + i;
    if (! save->set(image, frame)) {
      error(Str("Cannot save \"%1\", frame %2.").arg(_out).arg(frame));
      exitValue(1);
      break;
    }

    // Statistics.
    //
    if (! _silent)
      if (length > 1) {
        frameTimer.check();
        print(
          Str("frame %1 = %2 seconds (%3 fps)").
            arg(frame).
            arg(frameTimer.seconds(), 0, 2).
            arg(frameTimer.fps(), 0, 2)
        );
      }
  }
  if (i < length) break;
  if (! _silent) {
    totalTimer.check();
    print(
      Str("elapsed = %1 seconds (%2 fps)").
        arg(totalTimer.seconds(), 0, 2).
        arg(length / totalTimer.seconds(), 0, 2)
    );
  }

  r = true;
} while (0);
  if (load) delete load;
  if (save) delete save;
  return r;
}

Str App::cmdln(StrList & in)
{
  StrList argList;
  while (in.size()) {
    const Str it = in[0];
    in.pop();
    
    // Transform options.
    //
    if (Str("-crop") == it) {
      if (! inflateStr(in, &_cropArea)) return it;
    } else if (Str("-mirror") == it) {
      if (! inflateStr(in, &_mirror, imageOpMirrorStr())) return it;
    } else if (Str("-rotate90") == it) {
      if (! inflateStr(in, &_rotate90, imageOpRotate90Str())) return it;
    } else if (Str("-scale") == it) {
      if (! inflateStr(in, &_scaleSize)) return it;
    } else if (Str("-scaleFilter") == it) {
      if (! inflateStr(in, &_scaleFilterX, imageOpScaleFilterStr())) return it;
      if (! inflateStr(in, &_scaleFilterY, imageOpScaleFilterStr())) return it;
    } else if (Str("-scaleEdge") == it) {
      if (! inflateStr(in, &_scaleEdgeX, imageEdgeStr())) return it;
      if (! inflateStr(in, &_scaleEdgeY, imageEdgeStr())) return it;
    }
    
    // Load options.
    //
    else if (Str("-load") == it || Str("-l") == it) {
      if (! inflateStr(in, &_load)) return it;
    } else if (Str("-loadAttr") == it || Str("-la") == it) {
      Str plugin, attr, value;
      if (! inflateStr(in, &plugin)) return it;
      if (! inflateStr(in, &attr)) return it;
      if (! inflateStr(in, &value)) return it;
      _loadAttr[plugin].set(attr, deflateStr(value));
    } else if (Str("-loadFormat") == it || Str("-lf") == it) {
      PIXEL_FORMAT value = PIXEL_FORMAT(0);
      if (! inflateStr(in, &value, pixelFormatStr())) return it;
      _loadFormat = new PIXEL_FORMAT(value);
    } else if (Str("-loadType") == it || Str("-lt") == it) {
      PIXEL_TYPE value = PIXEL_TYPE(0);
      if (! inflateStr(in, &value, pixelTypeStr())) return it;
      _loadType = new PIXEL_TYPE(value);
    } else if (Str("-proxy") == it || Str("-p") == it) {
      if (! inflateStr(in, &_proxy)) return it;
    }
    
    // Save options.
    //
    else if (Str("-save") == it || Str("-s") == it) {
      if (! inflateStr(in, &_save)) return it;
    } else if (Str("-saveAttr") == it || Str("-sa") == it) {
      Str plugin, attr, value;
      if (! inflateStr(in, &plugin)) return it;
      if (! inflateStr(in, &attr)) return it;
      if (! inflateStr(in, &value)) return it;
      _saveAttr[plugin].set(attr, deflateStr(value));
    } else if (Str("-saveFormat") == it || Str("-sf") == it) {
      PIXEL_FORMAT value = PIXEL_FORMAT(0);
      if (! inflateStr(in, &value, pixelFormatStr())) return it;
      _saveFormat = new PIXEL_FORMAT(value);
    } else if (Str("-saveType") == it || Str("-st") == it) {
      PIXEL_TYPE value = PIXEL_TYPE(0);
      if (! inflateStr(in, &value, pixelTypeStr())) return it;
      _saveType = new PIXEL_TYPE(value);
    } else if (Str("-time") == it) {
      _time = new int [2];
      int value = 0;
      if (! inflateStr(in, &value)) return it;
      _time[0] = value;
      if (! inflateStr(in, &value)) return it;
      _time[1] = value;
    } else if (Str("-speed") == it) {
      double value = 0.0;
      if (! inflateStr(in, &value)) return it;
      _speed = new double(value);
    }
    
    // General options.
    //
    else if (it == Str("-silent")) {
      _silent = true;
    }
    
    else argList += it;
  }
  
  // Arguments.
  //
  if (! argList.size()) return Str("input");
  _in = argList.pop();
  if (! argList.size()) return Str("output");
  _out = argList.pop();
  if (argList.size()) return argList[0];
  
  return Str();
}

static const char helpStr [] =
"\n"
"djop\n"
"----\n"
"\n"
"Image processing utility.\n"
"\n"
"Usage\n"
"-----\n"
"\n"
"djop (input) (output) [option]...\n"
"\n"
"Arguments\n"
"---------\n"
"\n"
"input - Input image.\n"
"\n"
"output - Output image.\n"
"\n"
"Transform Options\n"
"-----------------\n"
"\n"
"-crop (x) (y) (width) (height) - Crop image.\n"
"\n"
"-mirror (value) - Mirror image. Values: %1.\n"
"\n"
"-rotate90 (value) - Rotate image 90 degrees. Values: %2.\n"
"\n"
"-scale (width) (height) - Scale image.\n"
"\n"
"-scaleFilter (value) (value) - Scale filter. Values: %3. Default: %4 "
"%5.\n"
"\n"
"-scaleEdge (value) (value) - Scale edge. Values: %6. Default: %7 %8.\n"
"\n"
"Load Options\n"
"------------\n"
"\n"
"-load, -l (plugin) - Specify image load plugin. Plugins: %9.\n"
"\n"
"-loadAttr, -la (plugin) (attr) (value) - Set an image load attribute.\n"
"\n"
"-loadFormat, -lf (value) - Convert pixel format. Values: %10.\n"
"\n"
"-loadType, -lt (value) - Convert pixel type. Values: %11.\n"
"\n"
"-proxy, -p (value) - Proxy scale.\n"
"\n"
"Save Options\n"
"------------\n"
"\n"
"-save, -s (plugin) - Specify image save plugin. Plugins: %12.\n"
"\n"
"-saveAttr, -sa (plugin) (attr) (value) - Set an image save attribute.\n"
"\n"
"-saveFormat, -sf (value) - Convert pixel format. Values: %13.\n"
"\n"
"-saveType, -st (value) - Convert pixel type. Values: %14.\n"
"\n"
"-time (start) (end) - Set start and end frames.\n"
"\n"
"-speed (value) - Set speed in frames per second.\n"
"\n"
"General Options\n"
"---------------\n"
"\n"
"-silent - Do not display messages.\n"
"\n"
"-about - About message.\n"
"\n"
"-help, -h - Help message.\n"
"\n"
"Examples\n"
"--------\n"
"\n"
"Copy an image:\n"
"> djop input.sgi output.tga\n"
"\n"
"Copy an image sequence:\n"
"> djop input.1-100.sgi output.1-100.tga\n"
"\n"
"Renumber an image sequence:\n"
"> djop input.1-100.sgi output.0100-0200.sgi\n"
"\n"
"Convert an image:\n"
"> djop input.sgi output.sgi -saveFormat rgb -saveType u16\n"
"\n"
"Scale an image:\n"
"> djop input.sgi output.sgi -scale 2048 1556\n"
"\n"
"Save the current video lookup table to an editable ASCII file:\n"
"> djop $DISPLAY -load vlut tmp.ppm -saveAttr ppm mode ascii -rotate90 90\n"
"\n"
"Load the current video lookup table from the the previous example:\n"
"> djop tmp.ppm $DISPLAY -save vlut -rotate90 270\n";

void App::help()
{
  printFormatted(
    Str(helpStr).
      arg(join(imageOpMirrorStr(), Str(", "))).
      arg(join(imageOpRotate90Str(), Str(", "))).
      arg(join(imageOpScaleFilterStr(), Str(", "))).
        arg(imageOpScaleFilterStr()[_scaleFilterX]).
        arg(imageOpScaleFilterStr()[_scaleFilterY]).
      arg(join(imageEdgeStr(), Str(", "))).
        arg(imageEdgeStr()[_scaleEdgeX]).
        arg(imageEdgeStr()[_scaleEdgeY]).
      arg(join(pluginNameList(imageLoad().pluginList()), Str(", "))).
      arg(join(pixelFormatStr(), Str(", "))).
      arg(join(pixelTypeStr(), Str(", "))).
      arg(join(pluginNameList(imageSave().pluginList()), Str(", "))).
      arg(join(pixelFormatStr(), Str(", "))).
      arg(join(pixelTypeStr(), Str(", ")))
  );
}

}

int main(int argc, char ** argv)
{
  DJ_DBG_LOG();
  int exitValue = 0;
do {
  djop::App app(argc, argv);
  exitValue = app.exitValue();
} while (0);
  return exitValue;
}
