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

  Audacity: A Digital Audio Editor

  ImportRaw.cpp

  Dominic Mazzoni

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

#include "ImportRaw.h"
#include "Import.h"

#include "RawAudioGuess.h"

#include "../DirManager.h"
#include "../FileFormats.h"
#include "../Internat.h"
#include "../Prefs.h"
#include "../WaveTrack.h"

#include <math.h>

#include <wx/defs.h>
#include <wx/button.h>
#include <wx/choice.h>
#include <wx/intl.h>
#include <wx/msgdlg.h>
#include <wx/panel.h>
#include <wx/progdlg.h>
#include <wx/sizer.h>
#include <wx/stattext.h>
#include <wx/textctrl.h>
#include <wx/timer.h>

#include "sndfile.h"

class ImportRawDialog:public wxDialog {

  public:
   ImportRawDialog(wxWindow * parent,
                   int encoding, int channels,
                   int offset, double rate);
   ~ImportRawDialog();

   void OnOK(wxCommandEvent & event);
   void OnCancel(wxCommandEvent & event);
   void OnPlay(wxCommandEvent & event);
   void OnChoice(wxCommandEvent & event);

   // in and out
   int mEncoding;
   int mChannels;
   int mOffset;
   double mRate;
   double mPercent;

 private:

   wxButton   *mOK;
   wxChoice   *mEncodingChoice;
   wxChoice   *mEndianChoice;
   wxChoice   *mChannelChoice;
   wxTextCtrl *mOffsetText;
   wxTextCtrl *mPercentText;
   wxTextCtrl *mRateText;

   int         mNumEncodings;
   int        *mEncodingSubtype;
   wxString   *mEncodingString;

   DECLARE_EVENT_TABLE()
};

int ImportRaw(wxWindow *parent, wxString fileName,
              TrackFactory *trackFactory, Track ***outTracks,
              progress_callback_t progressCallback, void *userData)
{
   int encoding = 0; // Guess Format
   int numChannels = 0;
   sampleFormat format;
   sf_count_t offset = 0;
   int int_offset = 0;
   longSampleCount totalFrames;
   double rate = 44100.0;
   double percent = 100.0;
   SNDFILE *sndFile;
   SF_INFO sndInfo;
   int result;

   encoding = RawAudioGuess(fileName,
                            &int_offset, &numChannels);
   offset = (sf_count_t)int_offset;

   if (encoding <= 0) {
      // Unable to guess.  Use mono, 16-bit samples with CPU endianness
      // as the default.
      encoding = SF_FORMAT_RAW | SF_ENDIAN_CPU | SF_FORMAT_PCM_16;
      numChannels = 1;
      offset = 0;
   }

   ImportRawDialog dlog(parent, encoding, numChannels, (int)offset, rate);
   dlog.ShowModal();
   if (!dlog.GetReturnCode())
      return false;

   encoding = dlog.mEncoding;
   numChannels = dlog.mChannels;
   rate = dlog.mRate;
   offset = (sf_count_t)dlog.mOffset;
   percent = dlog.mPercent;

   memset(&sndInfo, 0, sizeof(SF_INFO));
   sndInfo.samplerate = (int)rate;
   sndInfo.channels = (int)numChannels;
   sndInfo.format = encoding | SF_FORMAT_RAW;

   #ifdef _UNICODE
      /* sf_open doesn't handle fn_Str() in Unicode build. May or may not actually work. */
      sndFile = sf_open(FILENAME(fileName).mb_str(), SFM_READ, &sndInfo);
   #else // ANSI
      sndFile = sf_open(FILENAME(fileName).fn_str(), SFM_READ, &sndInfo);
   #endif // Unicode/ANSI

   if (!sndFile) {
      // TODO: Handle error
      char str[1000];
      sf_error_str((SNDFILE *)NULL, str, 1000);
      printf("%s\n", str);

      return 0;
   }

   result = sf_command(sndFile, SFC_SET_RAW_START_OFFSET, &offset, sizeof(offset));
   if (result != 0) {
      char str[1000];
      sf_error_str(sndFile, str, 1000);
      printf("%s\n", str);
   }

   sf_seek(sndFile, 0, SEEK_SET);
   
   totalFrames = (longSampleCount)(sndInfo.frames * percent / 100.0);

   //
   // Sample format:
   //
   // In general, go with the user's preferences.  However, if
   // the file is higher-quality, go with a format which preserves
   // the quality of the original file.
   //
   
   format = (sampleFormat)
      gPrefs->Read(wxT("/SamplingRate/DefaultProjectSampleFormat"), floatSample);

   if (format != floatSample &&
       sf_subtype_more_than_16_bits(encoding))
      format = floatSample;

   WaveTrack **channels = new WaveTrack *[numChannels];

   int c;
   for (c = 0; c < numChannels; c++) {
      channels[c] = trackFactory->NewWaveTrack(format);
      channels[c]->SetRate(rate);

      if (numChannels > 1)
         switch (c) {
         case 0:
            channels[c]->SetChannel(Track::LeftChannel);
            break;
         case 1:
            channels[c]->SetChannel(Track::RightChannel);
            break;
         default:
            channels[c]->SetChannel(Track::MonoChannel);
         }
   }

   if (numChannels == 2)
      channels[0]->SetLinked(true);

   sampleCount maxBlockSize = channels[0]->GetMaxBlockSize();
   bool cancelled = false;

   samplePtr srcbuffer = NewSamples(maxBlockSize * numChannels, format);
   samplePtr buffer = NewSamples(maxBlockSize, format);
   
   longSampleCount framescompleted = 0;
   
   long block;
   do {
      block = maxBlockSize;

      if (block + framescompleted > totalFrames)
         block = totalFrames - framescompleted;
      
      if (format == int16Sample)
         block = sf_readf_short(sndFile, (short *)srcbuffer, block);
      else
         block = sf_readf_float(sndFile, (float *)srcbuffer, block);

      if (block) {
         for(c=0; c<numChannels; c++) {
            if (format==int16Sample) {
               for(int j=0; j<block; j++)
                  ((short *)buffer)[j] =
                     ((short *)srcbuffer)[numChannels*j+c];
            }
            else {
               for(int j=0; j<block; j++)
                  ((float *)buffer)[j] =
                     ((float *)srcbuffer)[numChannels*j+c];
            }
            
            channels[c]->Append(buffer, format, block);
         }
         framescompleted += block;
      }

      if( progressCallback )
         cancelled = progressCallback(userData, framescompleted*1.0 / totalFrames);
      if (cancelled)
         break;
      
   } while (block > 0 && framescompleted < totalFrames);

   sf_close(sndFile);

   if (cancelled) {
      for (c = 0; c < numChannels; c++)
         delete channels[c];
      delete[] channels;

      return 0;
   }
   else {
      for (c = 0; c < numChannels; c++)
         channels[c]->Flush();

      *outTracks = (Track **)channels;

      return numChannels;
   }
}

//
// ImportRawDialog
//

enum {
   ChoiceID = 9000,
   PlayID
};

BEGIN_EVENT_TABLE(ImportRawDialog, wxDialog)
   EVT_BUTTON(wxID_OK, ImportRawDialog::OnOK)
   EVT_BUTTON(wxID_CANCEL, ImportRawDialog::OnCancel)
   EVT_BUTTON(PlayID, ImportRawDialog::OnPlay)
   EVT_CHOICE(ChoiceID, ImportRawDialog::OnChoice)
END_EVENT_TABLE()

ImportRawDialog::ImportRawDialog(wxWindow * parent,
                                 int encoding, int channels,
                                 int offset, double rate)
   :wxDialog(parent, -1, _("Import Raw Data"),
             wxDefaultPosition, wxDefaultSize,
             wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER),
    mEncoding(encoding), mChannels(channels),
    mOffset(offset), mRate(rate)
{
   wxBoxSizer *mainSizer, *hSizer, *vSizer;
   wxFlexGridSizer *gridSizer;
   wxButton *button;
   int selection, i;

   mainSizer = new wxBoxSizer(wxVERTICAL);
   
   hSizer = new wxBoxSizer(wxHORIZONTAL);

   //
   // Left-Side Controls (for manually selecting format)
   //

   vSizer = new wxBoxSizer(wxVERTICAL);

   // Encoding Choice

   mNumEncodings = 0;
   mEncodingSubtype = new int[sf_num_encodings()];
   mEncodingString = new wxString[sf_num_encodings()];

   selection = 0;
   for(i=0; i<sf_num_encodings(); i++) {
      SF_INFO info;
      memset(&info, 0, sizeof(SF_INFO));

      int subtype = (int)sf_encoding_index_to_subtype(i);
      info.format = SF_FORMAT_RAW + SF_ENDIAN_LITTLE + subtype;
      info.channels = 1;
      info.samplerate = 44100;

      if (sf_format_check(&info)) {
         mEncodingSubtype[mNumEncodings] = subtype;
         mEncodingString[mNumEncodings] = sf_encoding_index_name(i);;

         if ((mEncoding & SF_FORMAT_SUBMASK) == subtype)
            selection = mNumEncodings;

         mNumEncodings++;
      }
   }

   mEncodingChoice = 
      new wxChoice(this, ChoiceID,
                   wxDefaultPosition, wxDefaultSize,
                   mNumEncodings, mEncodingString);
   mEncodingChoice->SetSelection(selection);
   vSizer->Add(mEncodingChoice, 0, wxALIGN_LEFT | wxALL, 5);

   // Endian choice

   wxString endianStrings[4] =
      {/* i18n-hint: Refers to byte-order.  Don't translate "endianness" if you don't
          know the correct technical word. */
         _("No endianness"),
         /* i18n-hint: Refers to byte-order.  Don't translate this if you don't
          know the correct technical word. */
       _("Little-endian"),
         /* i18n-hint: Refers to byte-order.  Don't translate this if you don't
            know the correct technical word. */
       _("Big-endian"),
         /* i18n-hint: Refers to byte-order.  Don't translate "endianness" if you don't
            know the correct technical word. */
       _("Default endianness")};
   mEndianChoice =
      new wxChoice(this, ChoiceID,
                   wxDefaultPosition, wxDefaultSize,
                   4, endianStrings);
   switch(mEncoding & (SF_FORMAT_ENDMASK)) {
   case SF_ENDIAN_FILE:
      mEndianChoice->SetSelection(0);
      break;
   case SF_ENDIAN_LITTLE:
      mEndianChoice->SetSelection(1);
      break;
   case SF_ENDIAN_BIG:
      mEndianChoice->SetSelection(2);
      break;
   case SF_ENDIAN_CPU:
      mEndianChoice->SetSelection(3);
      break;
   }
   vSizer->Add(mEndianChoice, 0, wxALIGN_LEFT | wxALL, 5);

   // Channels choice

   wxString channelStrings[16];
   channelStrings[0].Printf(_("1 Channel (Mono)"));
   channelStrings[1].Printf(_("2 Channels (Stereo)"));
   for(i=2; i<16; i++) {
      channelStrings[i].Printf(_("%d Channels"), i+1);
   }
   mChannelChoice =
      new wxChoice(this, ChoiceID,
                   wxDefaultPosition, wxDefaultSize,
                   16, channelStrings);
   mChannelChoice->SetSelection(mChannels-1);
   vSizer->Add(mChannelChoice, 0, wxALIGN_LEFT | wxALL, 5);

   // Offset text

   gridSizer = new wxFlexGridSizer(3, 0, 0);
   
   gridSizer->Add(new wxStaticText(this, 0, _("Start offset:")),
                  0, wxALIGN_LEFT | wxALL, 5);

   mOffsetText = new wxTextCtrl(this, 0, wxString::Format(wxT("%d"), mOffset));
   gridSizer->Add(mOffsetText, 0, wxALIGN_LEFT | wxALL, 5);

   gridSizer->Add(new wxStaticText(this, 0, _("bytes")),
                  0, wxALIGN_LEFT | wxALL, 5);

   // Percent text

   gridSizer->Add(new wxStaticText(this, 0, _("Amount to import:")),
                  0, wxALIGN_LEFT | wxALL, 5);

   mPercentText = new wxTextCtrl(this, 0, wxT("100"));
   gridSizer->Add(mPercentText, 0, wxALIGN_LEFT | wxALL, 5);

   gridSizer->Add(new wxStaticText(this, 0, wxT("%")),
                  0, wxALIGN_LEFT | wxALL, 5);

   // Rate text

   gridSizer->Add(new wxStaticText(this, 0, _("Sample rate:")),
                  0, wxALIGN_LEFT | wxALL, 5);

   mRateText = new wxTextCtrl(this, 0, wxString::Format(wxT("%d"), (int)mRate));
   gridSizer->Add(mRateText, 0, wxALIGN_LEFT | wxALL, 5);

   /* i18n-hint: This is the abbreviation for "Hertz", or
      cycles per second. */
   gridSizer->Add(new wxStaticText(this, 0, _("Hz")),
                  0, wxALIGN_LEFT | wxALL, 5);

   vSizer->Add(gridSizer, 0, wxALIGN_LEFT | wxALL, 0);

   hSizer->Add(vSizer, 0, wxALIGN_TOP | wxALL, 5);

   //
   // Preview Pane goes here
   //

   wxPanel *p = new wxPanel(this, 0, wxDefaultPosition, wxSize(100, 100));
   hSizer->Add(p, 0, wxALIGN_TOP | wxALL, 5);

   mainSizer->Add(hSizer, 0, wxALIGN_CENTER | wxALL, 5);

   //
   // Button row
   //

   hSizer = new wxBoxSizer(wxHORIZONTAL);

   /*
   button = new wxButton(this, PlayID, _("Play"));
   hSizer->Add(button, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
   */

   button = new wxButton(this, wxID_CANCEL, _("Cancel"));
   hSizer->Add(button, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);

   mOK = new wxButton(this, wxID_OK, _("Import"));
   hSizer->Add(mOK, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);

   mainSizer->Add(hSizer, 0, wxALIGN_CENTER | wxALL, 5);

   SetAutoLayout(true);
   SetSizer(mainSizer);

   mainSizer->Fit(this);
   mainSizer->SetSizeHints(this);

   Centre(wxBOTH);
}

ImportRawDialog::~ImportRawDialog()
{
   delete[] mEncodingSubtype;
   delete[] mEncodingString;
}

void ImportRawDialog::OnOK(wxCommandEvent & WXUNUSED(event))
{
   long l;
   
   mEncoding = mEncodingSubtype[mEncodingChoice->GetSelection()];
   mEncoding += (mEndianChoice->GetSelection() * 0x10000000);
   mChannels = mChannelChoice->GetSelection() + 1;
   mOffsetText->GetValue().ToLong(&l);
   mOffset = l;
   mPercentText->GetValue().ToDouble(&mPercent);
   mRateText->GetValue().ToDouble(&mRate);

   if (mChannels < 1 || mChannels > 16)
      mChannels = 1;
   if (mOffset < 0)
      mOffset = 0;
   if (mPercent < 0.0)
      mPercent = 0.0;
   if (mPercent > 100.0)
      mPercent = 100.0;
   if (mRate < 100.0)
      mRate = 100.0;
   if (mRate > 100000.0)
      mRate = 100000.0;

   EndModal(true);
}

void ImportRawDialog::OnCancel(wxCommandEvent & WXUNUSED(event))
{
   EndModal(false);
}

void ImportRawDialog::OnPlay(wxCommandEvent & WXUNUSED(event))
{
}

void ImportRawDialog::OnChoice(wxCommandEvent & WXUNUSED(event))
{
   SF_INFO info;

   memset(&info, 0, sizeof(SF_INFO));

   mEncoding = mEncodingSubtype[mEncodingChoice->GetSelection()];
   mEncoding += (mEndianChoice->GetSelection() * 0x10000000);

   info.format = mEncoding | SF_FORMAT_RAW;
   info.channels = mChannelChoice->GetSelection() + 1;
   info.samplerate = 44100;

   if (sf_format_check(&info)) {
      mOK->Enable(true);
      return;
   }

   // Try it with 1-channel
   info.channels = 1;
   if (sf_format_check(&info)) {
      mChannelChoice->SetSelection(0);
      mOK->Enable(true);
      return;
   }

   // Otherwise, this is an unsupported format
   mOK->Enable(false);
}


// Indentation settings for Vim and Emacs and unique identifier for Arch, a
// version control system. Please do not modify past this point.
//
// Local Variables:
// c-basic-offset: 3
// indent-tabs-mode: nil
// End:
//
// vim: et sts=3 sw=3
// arch-tag: 3e6ead61-1ef2-4518-a622-523f1efebb9a

