/*******************************************************************************
*                         Goggles Music Manager                                *
********************************************************************************
*           Copyright (C) 2006-2010 by Sander Jansen. All Rights Reserved      *
*                               ---                                            *
* This program 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.                                          *
*                                                                              *
* This program 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 this program.  If not, see http://www.gnu.org/licenses.           *
********************************************************************************/
#include "common.h"
#include "FXTextCodec.h"
#include <FX88591Codec.h>
#include <FX88592Codec.h>
#include <FX88593Codec.h>
#include <FX88594Codec.h>
#include <FX88595Codec.h>
#include <FX88596Codec.h>
#include <FX88597Codec.h>
#include <FX88598Codec.h>
#include <FX88599Codec.h>
#include <FX885910Codec.h>
#include <FX885911Codec.h>
#include <FX885913Codec.h>
#include <FX885914Codec.h>
#include <FX885915Codec.h>
#include <FX885916Codec.h>
#include <FXCP437Codec.h>
#include <FXCP850Codec.h>
#include <FXCP852Codec.h>
#include <FXCP855Codec.h>
#include <FXCP856Codec.h>
#include <FXCP857Codec.h>
#include <FXCP860Codec.h>
#include <FXCP861Codec.h>
#include <FXCP862Codec.h>
#include <FXCP863Codec.h>
#include <FXCP864Codec.h>
#include <FXCP865Codec.h>
#include <FXCP866Codec.h>
#include <FXCP869Codec.h>
#include <FXCP874Codec.h>
#include <FXCP1250Codec.h>
#include <FXCP1251Codec.h>
#include <FXCP1252Codec.h>
#include <FXCP1253Codec.h>
#include <FXCP1254Codec.h>
#include <FXCP1255Codec.h>
#include <FXCP1256Codec.h>
#include <FXCP1257Codec.h>
#include <FXCP1258Codec.h>
#include <FXKOI8RCodec.h>

#include "GMFilename.h"


const char * gmcodecnames[]={
  "7-bit Ascii",
  "UTF-8 Unicode",
  "ISO 8859-1",
  "ISO 8859-2",
  "ISO 8859-3",
  "ISO 8859-4",
  "ISO 8859-5",
  "ISO 8859-6",
  "ISO 8859-7",
  "ISO 8859-8",
  "ISO 8859-9",
  "ISO 8859-10",
  "ISO 8859-11",
  "ISO 8859-13",
  "ISO 8859-14",
  "ISO 8859-15",
  "ISO 8859-16",
  "CP-437",
  "CP-850",
  "CP-852",
  "CP-855",
  "CP-856",
  "CP-857",
  "CP-860",
  "CP-861",
  "CP-862",
  "CP-863",
  "CP-864",
  "CP-865",
  "CP-866",
  "CP-869",
  "CP-874",
  "CP-1250",
  "CP-1251",
  "CP-1252",
  "CP-1253",
  "CP-1254",
  "CP-1255",
  "CP-1256",
  "CP-1257",
  "CP-1258",
  "KOI8-R",
  NULL,
  };


using namespace GMFilename;

namespace GMFilename {


FXTextCodec * findcodec(const FXuint & codec,FXbool & utf8) {
  utf8=false;
  switch(codec)	{
    case ENCODING_ASCII   : return NULL; break;
    case ENCODING_UTF8    : utf8=true; return NULL; break;
    case ENCODING_8859_1  : return new FX88591Codec; break;
    case ENCODING_8859_2  : return new FX88592Codec; break;
    case ENCODING_8859_3  : return new FX88593Codec; break;
    case ENCODING_8859_4  : return new FX88594Codec; break;
    case ENCODING_8859_5  : return new FX88595Codec; break;
    case ENCODING_8859_6  : return new FX88596Codec; break;
    case ENCODING_8859_7  : return new FX88597Codec; break;
    case ENCODING_8859_8  : return new FX88598Codec; break;
    case ENCODING_8859_9  : return new FX88599Codec; break;
    case ENCODING_8859_10 : return new FX885910Codec; break;
    case ENCODING_8859_11 : return new FX885911Codec; break;
    case ENCODING_8859_13 : return new FX885913Codec; break;
    case ENCODING_8859_14 : return new FX885914Codec; break;
    case ENCODING_8859_15 : return new FX885915Codec; break;
    case ENCODING_8859_16 : return new FX885916Codec; break;
    case ENCODING_CP437   : return new FXCP437Codec; break;
    case ENCODING_CP850   : return new FXCP850Codec; break;
    case ENCODING_CP852   : return new FXCP852Codec; break;
    case ENCODING_CP855   : return new FXCP855Codec; break;
    case ENCODING_CP856   : return new FXCP856Codec; break;
    case ENCODING_CP857   : return new FXCP857Codec; break;
    case ENCODING_CP860   : return new FXCP860Codec; break;
    case ENCODING_CP861   : return new FXCP861Codec; break;
    case ENCODING_CP862   : return new FXCP862Codec; break;
    case ENCODING_CP863   : return new FXCP863Codec; break;
    case ENCODING_CP864   : return new FXCP864Codec; break;
    case ENCODING_CP865   : return new FXCP865Codec; break;
    case ENCODING_CP866   : return new FXCP866Codec; break;
    case ENCODING_CP869   : return new FXCP869Codec; break;
    case ENCODING_CP874   : return new FXCP874Codec; break;
    case ENCODING_CP1250  : return new FXCP1250Codec; break;
    case ENCODING_CP1251  : return new FXCP1251Codec; break;
    case ENCODING_CP1252  : return new FXCP1252Codec; break;
    case ENCODING_CP1253  : return new FXCP1253Codec; break;
    case ENCODING_CP1254  : return new FXCP1254Codec; break;
    case ENCODING_CP1255  : return new FXCP1255Codec; break;
    case ENCODING_CP1256  : return new FXCP1256Codec; break;
    case ENCODING_CP1257  : return new FXCP1257Codec; break;
    case ENCODING_CP1258  : return new FXCP1258Codec; break;
    case ENCODING_KOIR8   : return new FXKOI8RCodec; break;
    default               : return NULL;
    }
  return NULL;
  }


/*
    0) trim white spaces
    1) only want printable characters
    2) substitute spaces for underscores [optional]
    3) make everything lowercase [optional]
    4) do not use any of the shell dangerous character set \'\\#~!\"$&();<>|`^*?[]/
*/
static FXString filter(const FXString & input,const FXString & forbidden,FXuint options){
//  const FXString forbidden = "\'\\#~!\"$&();<>|`^*?[]/.:";  // Allowed  %+,-=@_{}
  FXString result;
  FXwchar w;
  FXint i;
  FXbool lastspace=false;
  for (i=0;i<input.length();i=input.inc(i)){
    w = input.wc(i);
    if (Unicode::isPrint(w)) {
      if (Unicode::isSpace(w)) {
        if (!lastspace) {
          if (options&NOSPACES)
            result+="_";
          else
            result.append(&w,1);
          lastspace=true;
          }
        }
      else if (options&LOWERCASE && Unicode::isUpper(w)){
        w = Unicode::toLower(w);
        result.append(&w,1);
        lastspace=false;
        }
      else if (Unicode::isAscii(w) && Ascii::isPunct(input[i])) {
        if (forbidden.find(input[i])==-1)
        result.append(&w,1);
        lastspace=false;
        }
      else {
        result.append(&w,1);
        lastspace=false;
        }
      }
    }
  return result.trim();
  }


/* convert UTF8 to given 8 bit codec. decompose if necessary */
static FXString convert_and_decompose(const FXString & input,FXTextCodec * codec) {
  register FXint i=0,j=0;
  FXint len;
  FXString result;
  FXString input_decompose;
  FXchar c;

  for (i=0;i<input.length();i=input.inc(i)){
    len = codec->utf2mb(&c,1,&input[i],input.extent(i));
    if (len>0 && c!=0x1A) {
      result+=c;
      }
    else {
      input_decompose.assign(&input[i],input.extent(i));
      input_decompose = decompose(input_decompose,DecCompat);
      for (j=0;j<input_decompose.length();j=input_decompose.inc(j)){
        len = codec->utf2mb(&c,1,&input_decompose[j],input_decompose.extent(j));
        if (len>0 && c!=0x1A) {
          result+=c;
          }
        }
      }
    }
  return result;
  }

/* convert UTF8 to given 7 bit assci */
static FXString convert_and_decompose(const FXString & input) {
  register FXint i=0;
  FXString result;
  FXString in = decompose(input,DecCanonical);
  for (i=0;i<in.length();i=in.inc(i)){
    if (Ascii::isAscii(in[i]) && Ascii::isPrint(in[i]) ) {
      result+=in[i];
      }
    }
  return result;
  }


static FXString to_8bit_codec(const FXString & input,FXTextCodec * codec,const FXString & forbidden,FXuint opts) {
  FXString result;

  /// Filter the input
  result = filter(input,forbidden,opts);

  /// Make sure it is properly composed. Should we do this?
  result = compose(result,DecCompat);

  /// convert to given codec.
  result = convert_and_decompose(result,codec);

  return result;
  }


static FXString to_8bit_ascii(const FXString & input,const FXString & forbidden,FXuint opts) {
  FXString result;

  /// Filter the input
  result = filter(input,forbidden,opts);

  /// Make sure it is properly composed. Should we do this?
  result = compose(result,DecCompat);

  /// convert to given codec.
  result = convert_and_decompose(result);

  /// Return result
  return result;
  }

static FXString to_utf8(const FXString & input,const FXString & forbidden,FXuint opts) {
  FXString result;

  /// Filter the input
  result = filter(input,forbidden,opts);

  /// Make sure it is properly composed. Should we do this?
  result = compose(result,DecCompat);

  return result;
  }


enum {
  FIELD_INVALID,
  FIELD_OK,
  FIELD_TRANSLATE
  };

static FXuint getField(FXchar field,const GMTrack & track,FXString & result) {
  FXuint status = FIELD_TRANSLATE;
  switch(field) {
    case 'T': result = track.title; break;
    case 'A': result = track.album; break;
    case 'P': result = track.album_artist; break;
    case 'p': result = track.artist; break;
    case 'G': result = track.genre; break;
    case 'N': result = GMStringFormat("%.2d",GMTRACKNO(track.no));
              status=FIELD_OK;
              break;
    case 'n': result = GMStringVal(GMTRACKNO(track.no));
              status=FIELD_OK;
              break;
    case 'd': if (GMDISCNO(track.no)>0)
                result = GMStringVal(GMDISCNO(track.no));
              else
                result.clear();
              status=FIELD_OK;
              break;
    case 'y': result = GMStringVal(track.year);
              status=FIELD_OK;
              break;
    default : status=FIELD_INVALID; break;
    };
  return status;
  }

FXbool create(FXString & result,const GMTrack & track, const FXString & format,const FXString & forbidden,const FXuint & options,FXTextCodec * textcodec,FXbool utf8) {
  register FXint i=0;
  FXint n;
  FXString path;
  FXwchar w;
  FXuint status;
  FXString field;

  /// Make sure we start with an empty string
  result = FXString::null;

  /// Expand Environment Variables and such...
  path = FXPath::expand(format);

  /// Make absolute
  if (!FXPath::isAbsolute(path)) {
    path = FXPath::absolute(FXPath::directory(track.mrl),path);
    }

  /// Simplify things
  path = FXPath::simplify(path);

  /// Parse the field entries
  for (i=0;i<path.length();i=path.inc(i)){
    if (path[i]=='%') {
      n = path.inc(i);
      if (n<path.length() && Ascii::isAscii(path[n])) {
        status = getField(path[n],track,field);
        if(status==FIELD_TRANSLATE) {
          if (textcodec)
            result+=to_8bit_codec(field,textcodec,forbidden,options);
          else if (utf8)
            result+=to_utf8(field,forbidden,options);
          else
            result+=to_8bit_ascii(field,forbidden,options);
          i=n;
          continue;
          }
        else if (status==FIELD_OK) {
          if (field.empty())
            result.trim();
          else
            result+=field;
          i=n;
          continue;
          }

        }
      }
    w = path.wc(i);
    result.append(&w,1);
    }

  /// Trim white space
  result.trim();

  /// Add extension
  result+=".";
  if (options&LOWERCASE_EXTENSION || options&LOWERCASE)
    result.append(FXPath::extension(track.mrl).lower());
  else
    result.append(FXPath::extension(track.mrl));
  return true;
  }


FXbool createDirectory(const FXString & path) {
  if (FXStat::exists(path)) {
    if (FXStat::isDirectory(path))
      return true;
    else
      return false;
    }
  else {
    if (FXPath::isTopDirectory(path) || !createDirectory(FXPath::upLevel(path))) return false;
    if (!FXDir::create(path)) return false;
    }
  return true;
  }

void parse(GMTrack & track,const FXString & mask,FXuint options) {
  FXint nsep=0,i,j;
  FXString input,field;
  FXString dir;
  FXchar sep,item;
  FXint beg=0,end=0;

  // Determine number of path separators
  for (i=0;i<mask.length();i++){
    if (mask[i]=='\\') i+=1;
    else if (mask[i]=='/') nsep++;
    }

  /// Remove path components from the front that are not part of the mask
  /// Or if the mask doesn't specifiy path components, we just take the title of the filename.
  if (nsep) {
    input=FXPath::title(track.mrl);
    FXString dir=FXPath::directory(track.mrl);
    for (i=0;i<nsep;i++) {
      input = FXPath::name(dir) + PATHSEPSTRING + input;
      dir=FXPath::upLevel(dir);
      }
    }
  else {
    input=FXPath::title(track.mrl);
    }

  // Start from fresh
  if (options&OVERWRITE)
    track.reset();

//  fxmessage("filename: %s\n",track.mrl.text());
//  fxmessage("mask: %s\n",mask.text());
//  fxmessage("input to scan: %s \n",input.text());


  for (i=0;i<mask.length()&&beg<input.length();i++) {
    if (mask[i]=='%' && ((i+1)<mask.length()) && ( i==0 || mask[i-1]!='\\')) {
      i+=1;
      item=mask[i];
      j=i+1;

      /// Determine Separator
      sep=' ';
      if (j>=mask.length()) {
        sep='\0';
        }
      else if (mask[j]!='%') {
        i++;
        if (mask[j]=='\\') {
          j++;i++;
          if (j<mask.length())
            sep=mask[j];
          else
            sep=' ';
          }
        else {
          sep=mask[j];
          }
        }

      /// Get substring until separator
      end=beg;
      while(input[end]!=sep && end<input.length()) end++;

      if (end-beg>0) {
        field=input.mid(beg,end-beg).simplify();
        if (options&REPLACE_UNDERSCORE) field.substitute('_',' ');
        switch(item) {
          case 'T' : if (options&OVERWRITE || track.title.empty()) track.title.adopt(field); break;
          case 'P' : if (options&OVERWRITE || track.album_artist.empty()) track.album_artist.adopt(field); break;
          case 'A' : if (options&OVERWRITE || track.album.empty()) track.album.adopt(field); break;
          case 'p' : if (options&OVERWRITE || track.artist.empty()) track.artist.adopt(field); break;
          case 'n' :
#if FOXVERSION >= FXVERSION(1,7,12)
          case 'N' : if (options&OVERWRITE) track.no = field.toInt(); break;
#else
          case 'N' : if (options&OVERWRITE) track.no = FXIntVal(field); break;
#endif
          }
        //fxmessage("%%%c=\"%s\"\n",item,field.text());
        beg=end+1;
        }
      }
    else {
      //fxmessage("eat %c: %s\n",mask[i],&input[beg]);
      while(input[beg]!=mask[i] && beg<input.length()) beg++;
      beg++;
      }
    }

  }



}
