/*

  Munipack - command line interface

  Copyright © 2012-3 F.Hroch (hroch@physics.muni.cz)

  This file is part of Munipack.

  Munipack 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.

  Munipack 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 Munipack.  If not, see <http://www.gnu.org/licenses/>.

*/

#include "munipack.h"
#include "fitsfn.h"
#include <wx/wx.h>
#include <wx/cmdline.h>
#include <wx/regex.h>
#include <wx/tokenzr.h>
#include <wx/filename.h>
#include <wx/file.h>
#include <wx/stream.h>
#include <wx/sstream.h>
#include <wx/txtstrm.h>
#include <wx/wfstream.h>
#include <iostream>

using namespace std;


void Munipack::CommonOutputSingle(MuniProcess *action, const MuniCmdLineParser& cmd, const wxString& name)
{
  wxString a;

  if( cmd.Found("verbose") )
    action->Write("VERBOSE = T");


  if( cmd.Found("b") )
    EnableBackup();

  if( cmd.Found("backup",&a) )
    SetControl(a);

  if( cmd.Found("suffix",&a) || cmd.Found("S",&a) )
    SetSuffix(a);

  // all above affects the output:
  if( cmd.Found("output",&a) || cmd.Found("o",&a) )
    WriteOutput(action,a);
  else if( ! name.IsEmpty() )
    WriteOutput(action,name+".fits");
  else
    WriteOutput(action,action->GetCommand()+".fits");

}

void Munipack::CommonOutputMultiple(MuniProcess *action, const MuniCmdLineParser& cmd)
{
  wxString a;

  if( cmd.Found("verbose") )
    action->Write("VERBOSE = T");

  if( cmd.Found("target-directory",&a) || cmd.Found("t",&a) ) {
    if( ! SetTargetDir(a) )
      wxLogFatalError("Failed to create the directory `"+a+"'.");
  }

  if( cmd.Found("b") )
    EnableBackup();

  if( cmd.Found("backup",&a) )
    SetControl(a);

  if( cmd.Found("suffix",&a) || cmd.Found("S",&a) )
    SetSuffix(a);

  if( cmd.Found("O") ) {

    SetAdvanced(true);

    if( cmd.Found("pattern",&a) )
      SetPattern(a);

    if( cmd.Found("mask",&a) )
      SetMask(a);

    if( cmd.Found("format",&a) )
      SetFormat(a);
  }

}

void Munipack::CommonOptionsBitpix(MuniProcess *action, const MuniCmdLineParser& cmd)
{
  long n;
  if( cmd.Found("bitpix",&n) || cmd.Found("B",&n) )
    action->Write("BITPIX = %ld",n);
}

void Munipack::CommonOptionsPhCorr(MuniProcess *action, const MuniCmdLineParser& cmd)
{
  double x;
  if( cmd.Found("tol-temp",&x) )
    action->Write("TOL_TEMPERATURE = %lf",x);

  if( cmd.Found("tol-exp",&x) )
    action->Write("TOL_EXPOSURE = %lf",x);
}

void Munipack::CommonOptionsArith(MuniProcess *action, const MuniCmdLineParser& cmd)
{
  if( cmd.Found("arith") )
    action->Write("MEAN = 'A'");
}


void Munipack::CommonOptionsAddKey(MuniProcess *action, const MuniCmdLineParser& cmd)
{
  wxString par;
  if( cmd.Found("K",&par) || cmd.Found("keys",&par) ) {

    // split by commas, perhaps will be messed by KEY='value1,value2',KEY1='xx'
    wxStringTokenizer tokenizer(par,",");
    while ( tokenizer.HasMoreTokens() ) {

      wxString token = tokenizer.GetNextToken();

      wxRegEx rf("(.*)=(.*)");
      wxASSERT(rf.IsValid());

      if( rf.Matches(token) )
	action->Write("ADDKEY = '"+rf.GetMatch(token,1)+"' '"+rf.GetMatch(token,2)+"'");
      else
     	wxLogError("Option `"+token+"' unrecognized.");
    }
  }
}


void Munipack::CommonOptionsPhq(MuniProcess *action, const MuniCmdLineParser& cmd)
{
  wxString a;

  if( cmd.Found("q",&a) || cmd.Found("quantity",&a) ) {

    wxString l;
    long n;

    apstr(a,n,l);
    action->Write("NQUANTITIES = %d",n);
    action->Write("QUANTITIES = " + l);
  }
}

void Munipack::apstr(const wxString& a, long& n, wxString& l)
{
  n = 0;
  l = "";

  wxStringTokenizer tokenizer(a,",");
  while( tokenizer.HasMoreTokens() ) {
    n++;
    l += " '"+tokenizer.GetNextToken()+"'";
  }
}


// -----

void Munipack::GetResult(const wxString& filename, wxString& name,
			 wxString& newname, wxString& backupname)
{

  // check any presence of predefined output name
  // we are looking for comma outside of square brackets ...
  int comma = wxNOT_FOUND;
  int b = 0;
  for(wxString::const_iterator c = filename.begin(); c != filename.end(); ++c ) {
    if( *c == '[' )
      b++;
    else if ( *c == ']' )
      b--;
    else if( *c == ',' && b == 0 )
      comma = c - filename.begin();
  }

  bool predefined = comma != wxNOT_FOUND;

  if( predefined ) {

    // x.fits,y.fits => orig = 'x.fits', result = 'y.fits'
    name = filename.Mid(0,comma);
    newname = GetOutput(filename.Mid(comma+1));
    backupname = GetBackup(newname);

  }
  else if ( advanced ) {

    backupname.Empty();
    newname.Empty();

    wxString xname, ext;
    FITSFileName::ParseExt(filename,xname,ext);

    if( format.IsEmpty() ) {

      wxRegEx rf(pattern);

      if( ! rf.IsValid() ) {
	wxLogError("Regular expression pattern `"+pattern+"' is invalid.");
      }

      if( rf.Matches(filename) ) {

	name = filename;

	int e = -1;

	if( ! mask.IsEmpty() ) {
	  newname = filename;
	  e = rf.Replace(&newname,mask);
	}

	if( e == -1 || e == 0 ) {
	  wxLogError("Regullar expression `"+mask+"'failed to replace.");
	}
      }
    }
    else {

      name = filename;
      newname.Printf(format,++ncount);
      backupname = GetBackup(newname);

    }
  }
  else {

    name = filename;
    if( target.IsEmpty() ) {
      newname.Empty();
      backupname = GetBackup(name);
    }
    else {
      newname = GetOutput(name);
      backupname = GetBackup(newname);
    }

  }
}


void Munipack::WriteFiles(MuniProcess *action,const wxString& xfilename)
{
  if( InputBy(action,xfilename,true) ) return;

  wxString filename,new_name,backup_name;
  GetResult(xfilename,filename,new_name,backup_name);

  // removes potential specification of HDU in format x.fits[1]
  wxString xname, ext;
  FITSFileName::ParseExt(filename,xname,ext);

  wxFileName fname(xname);
  if( ! fname.FileExists() ) {
    wxLogError("File `"+xname+"' does not exists. Skipping.");
    return;
  }

  if( ! fname.IsFileReadable() /*|| ! fname.IsFileWritable()*/ ) {
    wxLogError("File `"+xname+"' has no read access. Skipping.");
    return;
  }

  action->WriteFiles(filename,backup_name,new_name);
}

void Munipack::WriteFile(MuniProcess *action,const wxString& filename)
{
  if( InputBy(action,filename,false) ) return;
  action->WriteFile(filename);
}


bool Munipack::InputBy(MuniProcess *action, const wxString& filename, bool s)
{
  wxString lstname;
  if( filename.StartsWith("@",&lstname) || filename == "-" ) {

    bool remove = false;

    if( lstname.IsEmpty() ) {

      /* Input from standard input

         Use of the std.input interferes with running of external
         utilities due to wxProcess.Redirect().

	 Much more worse is to read the input with help of
         wxTextInputStream which (in destructor) releases (detach,
         close) global program std. input (wxFile::fd_stdin) which
         leads to crash (and random and undefined behavior) in MuniProcess
         at line:

             out << InputBuffer[i] << endl;

	 Therefore (also be a lazy man) we are uses wxFile class,
         which leaves std. input opended, to copy std. input
         to a temporary file. The file can be used without previous
         limitations.

      */

      size_t n, nlen = 1024;
      char buf[nlen];

      wxFileName filename;
      filename.AssignTempFileName("munipack_stdin_");
      if( ! filename.IsOk() )
	wxLogFatalError("Failed to create a temp filename `"+filename.GetFullPath()+"'.");

      lstname = filename.GetFullPath();

      wxFile f(wxFile::fd_stdin);
      wxFile o(lstname,wxFile::write);
      if( ! o.IsOpened() ) wxLogFatalError("Failed to open temp file `"+lstname+"'.");
      while( (n = f.Read(buf,nlen)) > 0 )
	o.Write(buf,n);
      o.Close();
      f.Detach();

      remove = true;
    }

    wxFileInputStream input(lstname);
    if( ! input.IsOk() )
      wxLogFatalError("Failed to open list file `"+lstname+"'.");

    wxTextInputStream text(input);
    while( input.IsOk() && input.CanRead() ) {
      wxString line = text.ReadLine();
      if( ! line.IsEmpty() ) {
	// Right, this code is functional but not too elegnant.
        // Planed replacemnet with help of other new class.
	if( s )
	  WriteFiles(action,line);
	else
	  WriteFile(action,line);
      }
    }

    if( remove )
      wxRemoveFile(lstname);

    return true;
  }
  return false;
}

wxString Munipack::GetOutput(const wxString& name) const
{
  FITSFileName ftname(name);
  wxString filename(ftname.GetBaseName());

  if( target == "" )
    return filename;
  else {
    wxFileName src(filename);
    wxFileName dest(target,src.GetFullName());
    return dest.GetFullPath();
  }
}

void Munipack::WriteOutput(MuniProcess *action,const wxString& name)
{
  wxString o(GetOutput(name));
  action->WriteOutput(o,GetBackup(o));
}

wxString Munipack::GetBackup(const wxString& name) const
{
  FITSFileName file(name);
  file.SetSuffix(suffix);
  file.SetControl(control);

  if( file.FileExists() )
    return file.GetBackup();
  else
    return "";
}


bool Munipack::SetTargetDir(const wxString& t)
{
  target = t;

  // create the target directory if one doesn't exists
  wxFileName d;
  d.AssignDir(target);
  if( d.DirExists() )
    return true;
  else
    return d.Mkdir(wxS_DIR_DEFAULT,wxPATH_MKDIR_FULL);
}
