'use strict'
http = require 'http'
readline = require 'readline'
chery = require 'cheerio'
unirest = require 'unirest'
sys = require 'sys'
exec = require('child_process').exec()
fs = require('fs')
celeri = require('celeri')
c = console
path = require('path');
asset = path.resolve.bind(null, __dirname, '..');
events = require('events')
util = require('util')

multimeter = require('pm2-multimeter')

class Url extends  events.EventEmitter
  constructor: (@data) ->
    this.setMaxListeners 1000
    url = @data.url
    url = "http://#{url}" if 'www' == url.substring(0,3) 
    if 'http' == url.substring(0, 4) then @url = url else @url = "http://www.nhaccuatui.com/#{url}"

  container: '.list'
  item: '.button_download'
  
  crawler: (cb) ->
    t = this
    this.emit 'start', @url 
   
    # Dunno how to get total length ?
    fake_progress = 1
    http
      .get @url, (res) ->
        # @TODO Has no idea how to get content-length if the server did't return it ;(. maybe by chunk of transfer encoding...
        #total_size = parseInt(res.headers['content-length'], 10)
        res.setEncoding 'utf8'
        # @TODO Implement pipe
        html = ''
        res.on 'data', (chunk) ->
          fake_progress++
          fake_progess = 84 if fake_progess == 100
          html += chunk
          #fake_progess = parseInt(chunk.length * 100 / total_size)
          t.emit 'data', fake_progress
          return
          #bar.percent fake_progress # 
        .on 'end', (data) -> 
          #t.emit 'url'
          #bar.percent 100
          t.emit 'data', 100
          t.find_song_item html
      .on 'error', (e) ->
        console.log e.message()
    return this
  
  # TODO, follow next page
  find_song_item : (html) ->
    $ = chery.load html
    song_total = $("#{this.container} #{this.item}").length
    console.log "Found #{song_total} songs"

    # @TODO Imrpove this logic with a set of rule from external file
    song_list = [] 
    $(@container)
      .find(@item)
      .each (index, elem) ->
        #console.log "Bài hát: #{$(elem).attr('reltitle')}"
        #console.log "Ca sĩ: #{$(elem).attr('singer')}"
        key = $(elem).attr 'key'
        if !key?
          return false
        title = $(elem).attr('title')
        title = title.split '-'
        singer = if title[1]? then title[1].trim() else ''
        song_list.push
          title: title[0].substring(16)
          #title: $(elem).attr('reltitle')
          singer: singer
          #rel: $(elem).attr('rel')
          key: key
          songurl: "http://www.nhaccuatui.com/download/song/#{key}"
    this.emit 'finish', song_list
    return 

class Playlist extends Url
  container: '.list_song_in_album'
  #container: '.list-song-plist, .list_song_in_album'
  #item: '.dl'
  item: '.button_download'

class Search extends Url
  constructor: (@data) ->
    q = @data.q
    if 'http' == q.substring(0,4) then @url =q else @url = "http://www.nhaccuatui.com/tim-kiem?q=#{q}"
  container: '.search_returns_list'

class Singer extends Url
  container: '.list_music'
  
class Download
  constructor: () ->
    @multi = {}
    @bars = []
  start : () ->
    #this.bar = new ProgressBar(':bar', { total: 10 })
  done : (result = true) ->
    if result
      process.stdout.write("\nDone. Enjoy music.\n")
    else
      process.stdout.write("\nAn error occured. Check syntax and URL.\n")
    #this.spinner.done true
    process.exit()
   
  # Start to process a command
  process : (cmd, data) ->
    @multi = multimeter(process)
    @multi.on('^C', process.exit)
    @multi.charm.reset()
    switch cmd
      when "singer" then url = new Singer data
      when "search" then url = new Search data
      when "playlist" then url = new Playlist data
      when "page" then url = new Search data
      else
        console.log "Invalid command"
        process.exit(false)
    
    bar = {}
    this.start()
    url.on('finish', this.download.bind(this))
      .on('start', ((url) ->
        @multi.write "Start to find songs.\n"
        @multi.write "Progress: \n"
        bar = @multi(13, 2,
          width: 20
          solid:
            text : '|'
            foreground : 'yellow'
            background : 'red'
          empty:
            text : ' '
        )
        return
      ).bind(this))
      .on('data', (data) ->
        bar.percent data
      ).crawler()
  # Download a list of URL pass from song      
  download: (song_list) ->
    that = this
    @multi.charm.reset()

    this.done() if song_list.length == 0
    number_of_downloaded = 0
    
    download_file = (song_index, song_url, to) ->
      file = fs.createWriteStream to
      #console.log "\nStart to download ...#{p[p.length - 1]}"
      #that.spinner "Processing...", p[p.length - 1] * 100 / p.length
      file.on 'finish', () ->
        number_of_downloaded++
        #celeri.progress('Finish..', number_of_downloaded * 100 / song_list.length)
        #console.log "Finish download file #{number_of_downloaded}/#{song_list.length}"
        that.done() if number_of_downloaded == song_list.length
        that.bars[song_index].percent 100 
      count_downloaded_size = 0
      request = http.get song_url, (response) ->
        total_length = parseInt(response.headers['content-length'], 10)
        #console.log "Content length = " + total_length
        response.on 'data', (chunk) ->
          count_downloaded_size += chunk.length
          that.bars[song_index].percent parseFloat(count_downloaded_size * 100 / total_length)
          #console.log "Download  #{count_downloaded_size/1024}KB of file #{p[p.length-1]}"
          #bar.percent   
        response.pipe(file)

    for s, song_index in song_list
      title = "#{song_index+1}. #{s.title}: \n"
      @multi.write "#{title}"
      @bars[song_index] = @multi(title.length, song_index + 1,
        width: 20
        solid:
          text : '|'
          foreground : 'yellow'
          background : 'red'
        empty:
          text : ' '
      )

      ((song_index) -> 
        unirest
          .get(s.songurl)
          .end (res) ->
            #console.log s.songurl
            song = JSON.stringify res.body
            song = JSON.parse song
            if !song.data.stream_url? || song.error_code == 8
              # StreamURL, notifiy it to get real URL
              unirest
              .post('http://www.nhaccuatui.com/ajax/stream')
              .send('type=playlist')
              .send("key=#{song_list[song_index].key}")
              .send("v=#{new Date().getTime()}")
              .end((res) ->
                song = JSON.parse res.body
                real_song_url = song.data.stream if song.error_code == 0
                if real_song_url?
                  p = real_song_url.split '/'
                  download_file song_index, real_song_url, "#{process.cwd()}/#{p[p.length - 1]}"
                else
                  number_of_downloaded++
                return 
              ) 
              return false

            real_song_url = song.data.stream_url
            p = real_song_url.split '/'
            download_file song_index, real_song_url, "#{process.cwd()}/#{p[p.length - 1]}"
      )(song_index)
          #request.on
    return true

exports.run = () ->
  pjson = require asset('package.json')
  console.log "\n
               NCT-Downloader #{pjson.version}                     \n
                                                                   \n
     .oo                        o            .oPYo.                \n
    .P 8                        8            8    8                \n
   .P  8 `o  o' .oPYo. .oPYo.  o8P .oPYo.    8      .oPYo. ooYoYo. \n
  oPooo8  `bd'  8    ' 8    8   8  8    8    8      8    8 8' 8  8 \n
 .P    8  d'`b  8    . 8    8   8  8    8    8    8 8    8 8  8  8 \n
.P     8 o'  `o `YooP' `YooP'   8  `YooP' 88 `YooP' `YooP' 8  8  8 \n
..:::::....:::..:.....::.....:::..::.....:..::.....::.....:..:..:..\n
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::\n
\n"
  celeri.usage 'ndown [command] keyword_in_quote or url or album..'
  if process.argv.length == 2 or ['help', 'casi', 'singer', 'timkiem', 'search', 'playlist', 'page', 'trang'].indexOf(process.argv[2]) == -1
    console.log "Hey, type: ndown help.\n"
    process.exit()

  d = new Download
  celeri.option(
    command: 'casi :url OR singer :url', 
    description: ' Download nhạc của 1 ca sĩ'
  , (data) -> d.process "singer", data)
  
  celeri.option(
    command: 'timkiem :q OR search :q', 
    description: ' Download nhạc dựa theo từ khoá. Ví du: '
  , (data) -> d.process "search", data)
  
  celeri.option(
    command: 'playlist :url', 
    description: ' Download nhạc của 1 playlist'
  , (data) -> d.process "playlist", data)
  
  celeri.option(
    command: 'trang :url OR page :url', 
    description: ' Download nhạc trên 1 trang cụ thể'
  , (data) -> d.process "page", data)
  # parse the command line args
  celeri.parse process.argv
