/*******************************************************************************
*                         Goggles Music Manager                                *
********************************************************************************
*           Copyright (C) 2009-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 "GMApp.h"

#include "FXPNGImage.h"
#include "FXBMPImage.h"
#include "FXJPGImage.h"
#include "FXGIFImage.h"

#include <errno.h>

#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/fcntl.h>

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

#include <expat.h>




class XSPFParser {
protected:
  XML_Parser   parser;
  FXint        element;
  FXStringList files;
  FXString     title;
public:
  static void xspf_element_start(void*,const XML_Char *,const XML_Char **);
  static void xspf_element_end(void*,const XML_Char *);
  static void xspf_element_data(void*,const XML_Char*,int);
public:
  enum {
    Elem_None,
    Elem_Playlist,
    Elem_Playlist_Title,
    Elem_Playlist_TrackList,
    Elem_Playlist_TrackList_Track,
    Elem_Playlist_TrackList_Track_Location,
    };
public:
  XSPFParser();
  ~XSPFParser();
  void parse(const FXString & buffer,FXStringList & mrl,FXString & title);
  };


XSPFParser::XSPFParser() : element(Elem_None) {
  parser = XML_ParserCreate(NULL);
  XML_SetUserData(parser,this);
  XML_SetElementHandler(parser,xspf_element_start,xspf_element_end);
  XML_SetCharacterDataHandler(parser,xspf_element_data);
  }

XSPFParser::~XSPFParser() {
  XML_ParserFree(parser);
  }

void XSPFParser::parse(const FXString & buffer,FXStringList & mrl,FXString & t) {
  if (XML_Parse(parser,buffer.text(),buffer.length(),1)==0) {
    // some error occured;
    }
  else {
    t=title;
    mrl.adopt(files);
    }
  }

void XSPFParser::xspf_element_start(void * ptr,const XML_Char * name,const XML_Char**/*atts*/) {
  XSPFParser * parser = static_cast<XSPFParser*>(ptr);
  FXASSERT(parser);
  switch(parser->element) {
    case Elem_None:
      {
        if (compare(name,"playlist")==0)
          parser->element = Elem_Playlist;
      } break;
    case Elem_Playlist:
      {
        if (compare(name,"title")==0)
          parser->element = Elem_Playlist_Title;
        else if (compare(name,"trackList")==0)
          parser->element = Elem_Playlist_TrackList;
        } break;

    case Elem_Playlist_TrackList:
      {
        if (compare(name,"track")==0)
          parser->element = Elem_Playlist_TrackList_Track;
       } break;

    case Elem_Playlist_TrackList_Track:
      {
        if (compare(name,"location")==0)
          parser->element = Elem_Playlist_TrackList_Track_Location;
       } break;
    default: break;
    }
  }

void XSPFParser::xspf_element_end(void * ptr,const XML_Char * name) {
  XSPFParser * parser = static_cast<XSPFParser*>(ptr);
  FXASSERT(parser);
  switch(parser->element) {
    case Elem_Playlist:
      {
        if (compare(name,"playlist")==0)
          parser->element=Elem_None;
      } break;
    case Elem_Playlist_TrackList:
      {
        if (compare(name,"trackList")==0)
          parser->element=Elem_Playlist;
      } break;
    case Elem_Playlist_Title:
      {
        if (compare(name,"title")==0)
          parser->element=Elem_Playlist;
      } break;
    case Elem_Playlist_TrackList_Track:
      {
        if (compare(name,"track")==0)
          parser->element = Elem_Playlist_TrackList;
       } break;
    case Elem_Playlist_TrackList_Track_Location:
      {
        if (compare(name,"location")==0)
          parser->element = Elem_Playlist_TrackList_Track;
       } break;
    default: break;
    }
  }


void XSPFParser::xspf_element_data(void * ptr,const XML_Char * data,int len) {
  XSPFParser * parser = static_cast<XSPFParser*>(ptr);
  FXASSERT(parser);
  if (parser->element==Elem_Playlist_TrackList_Track_Location) {
    FXString url(data,len);
    parser->files.append(url);
    }
  else if (parser->element==Elem_Playlist_Title) {
    parser->title.assign(data,len);
    }
  }


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



#define URL_UNSAFE   "#$-_.+!*'><()\\,%\""          // Always Encode
#define URL_RESERVED ";/?:@=&"              // Only encode if not used as reserved by scheme


// Encode url string
FXString gm_url_encode(const FXString& url){
  register FXint p=0;
  register FXint c;
  FXString result;
  while(p<url.length()){
    c=url[p++];
//    if(!Ascii::isAlphaNumeric(c) && ((c<=' ' || c>='{') || strchr(URL_UNSAFE URL_RESERVED,c))){
    if (!Ascii::isAlphaNumeric(c)){
      result.append('%');
#if FOXVERSION < FXVERSION(1,7,0)
      result.append(FXString::HEX[(c>>4)&15]);
      result.append(FXString::HEX[c&15]);
#else
      result.append(FXString::value2Digit[(c>>4)&15]);
      result.append(FXString::value2Digit[c&15]);
#endif
      continue;
      }
    result.append(c);
    }
  return result;
  }


FXdouble gm_parse_number(const FXString & str) {
  if (str.empty())
    return NAN;

/// FOX 1.7 has its own scanf and always uses C locale for number conversions.
#if FOXVERSION > FXVERSION(1,7,0)
  FXdouble value=NAN;
  if (str.scan("%lg",&value)==1)
    return value;
  else
    return NAN;
#else
  errno=0;
  FXfloat value = strtod(str.text(),NULL);
  if (errno) return NAN;
  return value;
#endif
  }


FXbool gm_buffer_file(const FXString & filename,FXString & buffer) {
  FXFile file(filename,FXIO::Reading);
  if (file.isOpen()) {

    buffer.assign('\0',file.size());

    return (file.readBlock((void*)buffer.text(),buffer.length())==buffer.length());
    }
  return false;
  }



void gm_parse_m3u(FXString & data,FXStringList & mrl) {
  FXint start=0,end=0,next;

  for (FXint i=0;i<data.length();i++) {
    if (data[i]=='\n') {
      end=i;
      next=i+1;

      /// Skip white space
      while(start<end && Ascii::isSpace(data[start])) start++;

      /// Skip white space
      while(end>start && Ascii::isSpace(data[end])) end--;

      /// Parse the actual line.
      if ((end-start)) {
        if (data[start]!='#') {
          mrl.append(data.mid(start,1+end-start));
          }
        }
      start=next;
      }
    }
  }


void gm_parse_pls(FXString & data,FXStringList & mrl) {
  FXint start=0,end=0,pos,next;
  for (FXint i=0;i<data.length();i++) {
    if (data[i]=='\n') {
      end=i;
      next=i+1;

      /// Skip white space
      while(start<end && Ascii::isSpace(data[start])) start++;

      /// Skip white space
      while(end>start && Ascii::isSpace(data[end])) end--;

      /// Parse the actual line.
      if ((end-start)>6) {
        if (compare(&data[start],"File",4)==0) {
          pos = data.find('=',start+4);
          if (pos==-1) continue;
          pos++;
          if (end-pos>0) {
            mrl.append(data.mid(pos,1+end-pos));
            }
          }
        }
      start=next;
      }
    }
  }







void gm_parse_xspf(FXString & data,FXStringList & mrl,FXString & title) {
  XSPFParser xspf;
  xspf.parse(data,mrl,title);
  }


FXbool gm_has_opengl() {
#if FOXVERSION < FXVERSION(1,7,15)
  int glminor,glmajor;
  return FXGLVisual::supported(GMApp::instance(),glmajor,glminor);
#else
  return FXGLVisual::hasOpenGL(GMApp::instance());
#endif
  }



void gm_focus_and_select(FXTextField * textfield) {
  FXASSERT(textfield->id());
  textfield->setFocus();
  if (!textfield->getText().empty())
    textfield->setSelection(0,textfield->getText().length());
  }

void gm_run_popup_menu(FXMenuPane*pane,FXint rx,FXint ry) {
  pane->create();
  pane->forceRefresh();
  pane->show();
  pane->grabKeyboard();
  pane->popup(NULL,rx,ry);
  FXApp::instance()->runPopup(pane);
  pane->ungrabKeyboard();
  }

void gm_set_window_cursor(FXWindow * window,FXCursor * cur) {
  window->setDefaultCursor(cur);
  window->setDragCursor(cur);
  FXWindow * child=window->getFirst();
  while(child) {
    child->setDefaultCursor(cur);
    child->setDragCursor(cur);
    child=child->getNext();
    }
  }



FXbool gm_is_local_file(const FXString & filename) {
  if (filename[0]=='/') return true;
  FXString scheme = GMURL::scheme(filename);
  if (scheme.empty() || (comparecase(scheme,"file")==0))
    return true;
  else
    return false;
  }



void gm_convert_filenames_to_uri(const FXStringList & filenames,FXString & uri){
  if (filenames.no()) {
    uri=GMURL::fileToURL(filenames[0]);
    for (FXint i=1;i<filenames.no();i++){
      uri+="\r\n";
      uri+=GMURL::fileToURL(filenames[i]);
      }
    }
  }

void gm_convert_uri_to_filenames(FXString & files,FXStringList & filelist){
  FXint begin,end;
  FXString file;
  for(begin=0;begin<files.length();begin=end+2){
    end=files.find_first_of("\r\n",begin);
    if (end<0) end = files.length();
    file = GMURL::decode(GMURL::fileFromURL(files.mid(begin,end-begin)));
    if (!file.empty()) filelist.append(file);
    }
  }

void gm_convert_filenames_to_gnomeclipboard(const FXStringList & filenames,FXString & uri){
  if (filenames.no()) {
    uri="copy\n" + GMURL::fileToURL(filenames[0]);
    for (FXint i=1;i<filenames.no();i++){
      uri+="\r\n";
      uri+=GMURL::fileToURL(filenames[i]);
      }
    }
  }

void gm_convert_gnomeclipboard_to_filenames(FXString & files,FXStringList & filelist){
  FXint begin,end;
  FXString file;
  for(begin=0;begin<files.length();begin=end+1){
    end=files.find_first_of("\r\n",begin);
    if (end<0) end = files.length();
    if (begin) {
      file = GMURL::decode(GMURL::fileFromURL(files.mid(begin,end-begin)));
      if (!file.empty()) filelist.append(file);
      }
    }
  }

void gm_make_absolute_path(const FXString & path,FXStringList & urls) {
  for (FXint i=0;i<urls.no();i++) {
    if (gm_is_local_file(urls[i]))
      urls[i]=GMURL::fileFromURL(urls[i]);
    if (!FXPath::isAbsolute(urls[i]))
      urls[i]=FXPath::absolute(path,urls[i]);
    }
  }



/******************************************************************************/
static FXbool gm_launch_program(const FXchar * const * programs,const FXString & url) {
  FXString path = FXSystem::getExecPath();
  FXString exec;

  for (int i=0;programs[i]!=NULL;i++){
    exec = FXPath::search(path,programs[i]);
    if (!exec.empty()) break;
    }

  if (exec.empty()) return false;

  exec += " " + FXPath::enquote(url);

  pid_t pid = fork();
  if (pid==-1){ /// Failure delivered to Parent Process
      return false;
      }
  else if (pid==0) { /// Child Process
      int i = sysconf(_SC_OPEN_MAX);
      while (--i >= 3) {
        close(i);
        }
      execlp("/bin/sh", "sh", "-c",exec.text(),(char *)0);
      exit(EXIT_FAILURE);
      }
  else { /// Parent Process
    return true;
      }
  return true;
  }

FXbool gm_open_browser(const FXString & url) {
  static const char * const programs[]={"xdg-open","chromium","firefox","konqueror","opera","netscape",NULL};
  return gm_launch_program(programs,url);
  }

FXbool gm_open_folder(const FXString & folder) {
  static const char * const programs[]={"xdg-open","thunar","dolphin","konqueror","nautilus",NULL};
  return gm_launch_program(programs,folder);
  }

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



FXImage * gm_load_image_from_data(const void * data,FXuval size,const FXString & mime,FXint scale) {
  FXImage * image = NULL;
//  GM_DEBUG_PRINT("%s: loading image with mimetype \"%s\"\n",__func__,mime.text());
  if ((comparecase(mime,"image/jpg")==0) || (comparecase(mime,"image/jpeg")==0) || (comparecase(mime,"JPG")==0)) {
    image=new FXJPGImage(FXApp::instance());
    }
  else if (comparecase(mime,FXPNGImage::mimeType)==0) {
    image=new FXPNGImage(FXApp::instance());
    }
  else if ((comparecase(mime,"image/bmp")==0) || (comparecase(mime,"image/x-bmp")==0) ) {
    image=new FXBMPImage(FXApp::instance());
    }
  else if ((comparecase(mime,FXGIFImage::mimeType)==0)) {
    image=new FXGIFImage(FXApp::instance());
    }
  else {
    GM_DEBUG_PRINT("%s: Mimetype \"%s\" not handled\n",__func__,mime.text());
    }

  if (image) {
    FXMemoryStream store;
#if FOXVERSION < FXVERSION(1,7,18)
    store.open(FXStreamLoad,size,(FXuchar*)data);
#else
    store.open(FXStreamLoad,(FXuchar*)data,size);
#endif
    if (image->loadPixels(store)) {
      if (scale) {
        if ((image->getWidth()>scale) || (image->getHeight()>scale)) {
          if (image->getWidth()>image->getHeight())
            image->scale(scale,(scale*image->getHeight())/image->getWidth(),1);
          else
            image->scale((scale*image->getWidth())/image->getHeight(),scale,1);
          }
        }
      store.close();
      return image;
      }
    store.close();
    delete image;
    }
  return NULL;
  }




FXbool gm_make_path(const FXString & path,FXuint perm) {
#if FOXVERSION < FXVERSION(1,7,0)
  if(!path.empty()){
    if(FXStat::isDirectory(path)) return true;
    if(gm_make_path(FXPath::upLevel(path),perm)){
      if(FXDir::create(path,perm)) return true;
      }
    }
  return false;
#else
  return FXDir::createDirectories(path,perm);
#endif
  }


FXbool gm_decode_base64(FXuchar * buffer,FXint & len){
  static const char base64[256]={
    0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
    0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
    0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x3e,0x80,0x80,0x80,0x3f,
    0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x80,0x80,0x80,0x80,0x80,0x80,
    0x80,0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,
    0x0f,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x80,0x80,0x80,0x80,0x80,
    0x80,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f,0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,
    0x29,0x2a,0x2b,0x2c,0x2d,0x2e,0x2f,0x30,0x31,0x32,0x33,0x80,0x80,0x80,0x80,0x80,
    0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
    0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
    0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
    0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
    0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
    0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
    0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
    0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80};

  FXuint  pos=0;
  FXuchar v;
  for (FXint i=0,b=0;i<len;i++) {
    v=base64[buffer[i]];
    if (v!=0x80) {
      switch(b) {
        case 0: buffer[pos]=(v<<2);
                b++;
                break;
        case 1: buffer[pos++]|=(v>>4);
                buffer[pos]=(v<<4);
                b++;
                break;
        case 2: buffer[pos++]|=(v>>2);
                buffer[pos]=(v<<6);
                b++;
                break;
        case 3: buffer[pos++]|=v;
                b=0;
                break;
        }
      }
    else {
      if (buffer[i]=='=' && b>1) {
        len=pos;
        return true;
        }
      else {
        return false;
        }
      }
    }
  len=pos;
  return true;
  }

