fs = require('fs')
path = require('path')
remote = require('remote')
imageSize = require('image-size')

#compressFile = remote.getGlobal('compressFile')
_rd = remote.getGlobal('FOLDERS')
FOLDERS =
    HTML: _rd.HTML
    BACKUP: _rd.BACKUP
    UTILS: _rd.UTILS
    IMG: _rd.IMG

showOpenDialog = remote.getGlobal('showOpenDialog')
openDevTools = remote.getGlobal('openDevTools')
getPort = remote.getGlobal('port')

app = angular.module 'app', ['mgcrea.ngStrap']

init = ->
    window.app = app
    window.openExternal = remote.getGlobal('openExternal')
    window.wsManager = WebSocketManager()
    window.FOLDERS = FOLDERS
    window.checkVIP = -> return wsManager.ws.send wsManager.EVENT_REQ.CHECK_VIP().json

disableEvent = (e) ->
    if e
        e.preventDefault()
        e.stopPropagation()
    return

document.addEventListener 'drop', disableEvent
document.addEventListener 'dragover', disableEvent

## --- hissatsu open debug console ---
keyUpList = []
document.addEventListener 'keyup', (e) ->
    arraySame = (a, b) ->
        return false if a.length != b.length
        ret = true
        ret = false for i in [0...a.length] when a[i] != b[i]
        return ret
    [left, up, right, down, key_a, key_b] = [37, 38, 39, 40, 65, 66]
    hissatsu = [up, up, down, down, left, right, left, right, key_b, key_a]

    keyUpList.push e.keyCode
    return if hissatsu.length != keyUpList.length
    if arraySame keyUpList, hissatsu
        openDevTools()
        keyUpList = []
    else
        keyUpList.shift()
## --- hissatsu open debug console end ---


WebSocketManager = ->
    wsUrl = 'ws://localhost:' + getPort() + '/echo'
    ws = new WebSocket(wsUrl)
    messageHandlers = []
    connectedHandlers = []
    ws.onmessage = (event)->
        console.log "[App WebSocketManager WebSocketManager] <--", event.data
        return if event.data == 'ok'

        message = JSON.parse(event.data)
        handler(message) for handler in messageHandlers

    ws.onopen = ->
        console.log "[App WebSocketManager WebSocketManager] ==="
        handler() for handler in connectedHandlers

    EVENT_REQ =
        _format: (cmd) ->
            ret =
                cmd: cmd
                json: JSON.stringify(cmd)

        COMPRESS_FILE: (fullPath) ->
            cmd =
                event: 'new_file'
                payload:
                    path: fullPath
            return EVENT_REQ._format(cmd)

        CREATE_AUTH: (email, password) ->
            cmd =
                event: 'create_auth'
                payload:
                    email: email
                    password: password
            return EVENT_REQ._format(cmd)

        CHECK_VIP: ->
            cmd =
                event: 'check_vip'
                payload: ''
            return EVENT_REQ._format(cmd)

        COMPRESSED_COUNT: ->
            cmd =
                event: 'compressed_count'
                payload: ''
            return EVENT_REQ._format(cmd)

    EVENT_RES =
        COMPRESS_PROGRESS: 'compress_progress'
        CREATE_AUTH: 'create_auth'
        CHECK_VIP: 'check_vip'
        COMPRESSED_COUNT: 'compressed_count'

    ret =
        EVENT_REQ: EVENT_REQ
        EVENT_RES: EVENT_RES
        ws: ws
        addMessageHandler: (handler) -> messageHandlers.push handler if messageHandlers.indexOf(handler) < 0
        addConnectedHandler: (handler) -> connectedHandlers.push handler
    return ret


init()

app.filter 'fileName', ->
    ret = (fullName) ->
        index = fullName.lastIndexOf('.')
        return fullName if index < 0
        return fullName[0...index]
    return ret

app.filter 'fileSize', ->
    ret = (bytes, number) ->
        return null if not bytes
        number = 2 if not number
        units = ['B', 'K', 'M', 'G']

        for unit in units
            return bytes.toFixed(number) + unit if Math.abs(bytes) < 1024
            bytes /= 1024
    return ret

app.directive 'moveCenter', ->
    ret =
        scope:
            bind: "=moveCenter"
        link: (scope, element, attrs)->
            el = element[0]
            scope.$watch (-> scope.bind), ->
                if scope.bind
                    el.style.setProperty 'left', '50%', null
                    el.style.setProperty 'top', '50%', null
                    scope.bind = false
                else
                    el.style.setProperty 'left', '', null
                    el.style.setProperty 'top', '', null
    return ret

app.directive 'dragMove', ->
    ret =
        link: (scope, element)->
            el = element[0]
            fix_x = 0
            fix_y = 0
            el.addEventListener 'mousedown', (e)->
                fix_x = el.offsetLeft - e.clientX
                fix_y = el.offsetTop - e.clientY
                document.addEventListener 'mouseup', onMouseUp
                document.addEventListener 'mousemove', onMouseMove

            onMouseUp = ->
                fix_x = 0
                fix_y = 0
                document.removeEventListener 'mouseup', onMouseUp
                document.removeEventListener 'mousemove', onMouseMove

            onMouseMove = (e)->
                replaceCss =
                    left: (e.clientX + fix_x) + 'px'
                    top: (e.clientY + fix_y) + 'px'
                element.css replaceCss
                return
    return ret

app.directive 'getElement', ->
    ret =
        link: (scope, element, attrs)->
            scope[attrs.getElement] = element[0]
    return ret

app.directive 'disableChildDrag', ->
    ret =
        link: (scope, element)->
            allChildren = (ele)->
                _ret = []
                for e in ele.children()
                    childElement = angular.element(e)
                    _ret.push childElement
                    _ret = _ret.concat allChildren(childElement) if childElement.children().length > 0
                return _ret

            el[0].setAttribute('draggable', 'false') for el in allChildren(element)
            return
    return ret


app.directive 'filesDrop', ->
    ret =
        scope:
            drop: '=filesDrop'
        link: (scope, element)->
            el = element[0]
            el.addEventListener 'drop', (e)->
                e.preventDefault()
                console.log e.dataTransfer.types
                files = e.dataTransfer.files
                scope.drop(files)
            return
    return ret


app.directive 'piePath', ($timeout)->
    ret =
        scope:
            rate: '@piePath'
        link: (scope, element)->
            el = element[0]

            scope.$watch (->return el.parentElement.offsetWidth + el.parentElement.offsetHeight + scope.rate), ()->
                return if scope.rate < 0.001
                return if el.parentElement.offsetWidth == 0 or el.parentElement.offsetHeight == 0

                draw = ->
                    width = el.parentElement.offsetWidth
                    height = el.parentElement.offsetHeight
                    r = Math.min(width, height) / 2
                    x = r
                    y = r
                    pi = 3.1415926
                    angle = pi / 2 - 2 * pi * scope.rate
                    endX = r + r * Math.cos(angle)
                    endY = r - r * Math.sin(angle)

                    longArcFlag = 0
                    longArcFlag = 1 if endX < x # 在左边的时候, 设为长弧

                    piePath = "M#{x},#{y} L#{x},0 A#{r},#{r} 0 #{longArcFlag},1 #{endX},#{endY} z"
                    el.setAttribute('d', piePath)
                    #                    console.log '[App piePath] r, rate, endX, endY', r, scope.rate, endX, endY
                    return

                $timeout (-> draw()), 10 #offsetWidth在切换ngshow时, 还未出现, 因此延时
                return


app.directive 'bindLeftBtn', ($timeout)->
    ret =
        scope:
            isFront: "=bindLeftBtn"
        link: (scope, element)->
            el = element[0]
            el.addEventListener 'mousedown', (e)->
                scope.isFront = true
                scope.$apply()


            el.addEventListener 'mouseup', (e)->
                scope.isFront = false
                scope.$apply()
    return ret

app.directive 'bindKeySpace', ($timeout)->
    ret =
        scope:
            isFront: "=bindKeySpace"
        link: (scope, element)->
            onKeyDown = (e)->
                return if e.keyCode != 32 # space
                scope.isFront = true
                scope.$apply()
                e.preventDefault()
                document.removeEventListener 'keydown', onKeyDown


            document.addEventListener 'keyup', (e)->
                return if e.keyCode != 32 # space
                scope.isFront = false
                scope.$apply()
                e.preventDefault()
                document.addEventListener 'keydown', onKeyDown

            document.addEventListener 'keydown', onKeyDown
    return ret

app.factory 'Redirector', ($rootScope)->
    views =
        drop: 'drop-view'
        file: 'file-view'
    location = views.drop
    ret =
        views: views
        go: (view) ->
            location = view
        isCurrentView: (view)->
            return location == view

    return ret


app.factory 'FileStatusService', ($rootScope, $timeout)->
    model = [] # [{fullPath:xxx, fileName:xxx directory:xxx size:xxx compressed:xxx progress:xxx}]
    PROGRESS =
        NOT_START: 1
        COMPRESSING: 2
        COMPRESS_ERROR: 4
        WAITING: 8
        LIMIT: 16
        ERROR_FORMAT: 32
        COMPRESSED_LARGER: 64
        COMPRESSED: 128

    pushedCount = 0

    searchModel = (cmpValue) ->
        return fileInfo for fileInfo in model when cmpValue(fileInfo)
        return null

    pushModel = (data) ->
        model.push data
        $timeout (-> $rootScope.$apply()), 0

    backupFullPath = (fullPath) ->
        return path.join(FOLDERS.BACKUP, path.parse(fullPath).base)

    backup = (fullPath) ->
        fs.createReadStream(fullPath).pipe(fs.createWriteStream(backupFullPath(fullPath)))
        console.log '[App FileStatusService backup] copy', fullPath

    messageHandler = (message)->
        m = searchModel((info)-> info['fullPath'] == message['full_path'])
        switch message.event
            when wsManager.EVENT_RES.COMPRESS_PROGRESS
                m.progress = message['progress']
                switch message['progress']
                    when PROGRESS.COMPRESSED
                        m.progress = PROGRESS.COMPRESSED
                        m.onCompressed()
                        $timeout (-> $rootScope.$apply()), 0

                    when PROGRESS.COMPRESSING
                        do (m)->
                            m.compressingRate = 0
                            updateCompressingRate = ->
                                r = m.compressingRate
                                m.compressingRate = r + (0.95 - r) * 0.02
                                return m.compressingRate = 1 if m.progress != PROGRESS.COMPRESSING
                                $timeout updateCompressingRate, 100
                            updateCompressingRate()
                $timeout (-> $rootScope.$apply()), 0
            when wsManager.EVENT_RES.COMPRESSED_COUNT
                pushedCount = message.value
                $timeout (-> $rootScope.$apply()), 0
            else
                return

    wsManager = window.wsManager
    wsManager.addMessageHandler messageHandler
    compressFile = (fullPath) ->
        wsManager.ws.send wsManager.EVENT_REQ.COMPRESS_FILE(fullPath).json

    FileLoader =
        tasks: []
        idle: true
        push: (fullPath, subFiles)->
            FileLoader.tasks.push [fullPath, subFiles]
            $timeout (->$rootScope.$apply()),10

        nextTask: ->
            console.log '[App FileStatusService FileLoader] nextTask tasks',(i[0] for i in FileLoader.tasks)
            fl = FileLoader
            if fl.tasks.length == 0
                fl.idle = true
                console.log '[App FileStatusService FileLoader] idle->true'
                FileLoader.taskFinished()
                return

            [fullPath, subFiles] = fl.tasks.shift()
            if subFiles
                fl.push path.join(fullPath, fileName) for fileName in subFiles
                return fl.nextTask()

            status = fs.lstatSync(fullPath)
            if status.isDirectory()
                files = fs.readdirSync(fullPath)
                return fl.nextTask() if files.length == 0
                fl.tasks.unshift [path.join(fullPath, files[0]), null]
                fl.push fullPath, files[1...] if files.length > 1
                return fl.nextTask()

            if status.isFile() and ret.rightExt(fullPath) and not ret.zeroSize(fullPath)
                if ret.limited()
                    fl.tasks = []
                    $rootScope.notifyBox.post 'fa fa-ban', '非VIP限制10张', 'http://www.ppduck.com/pay', '开通VIP' if ret.limited()
                    return fl.nextTask()

                existInfo = searchModel((info)-> info['fullPath'] == fullPath)
                if existInfo
                    ret.existsModel.push existInfo
                    return fl.nextTask()

                backup fullPath
                pushModel ret.makeFileInfo(fullPath)
                pushedCount++
                console.log '[App FileStatusService FileLoader] pushModel fullPath', fullPath

            $timeout  fl.nextTask, 10

        update: ->
            return if not FileLoader.idle
            FileLoader.idle = false
            FileLoader.nextTask()

        taskFinished: ->
            s = ret
            if s.existsModel.length > 0
                if confirm '是否重载已存在图片\n' + (i.pathInfo.base for i in s.existsModel).join('\n')
                    s.remove info for info in s.existsModel
                    s.load(info.fullPath) for info in s.existsModel
                else
                    $rootScope.notifyBox.post 'fa fa-exclamation', "#{s.existsModel.length}张己存在", '', ''
                s.existsModel = []

        start: ->
            $rootScope.$watch (->FileLoader.tasks.length), ->
                FileLoader.update()

    FileLoader.start()

    ThumbMaker =
        tasks: []
        idle: true
        push: (fullPah, cb)->
            ThumbMaker.tasks.push [fullPah, cb]

        nextTask: ->
            tm = ThumbMaker
            return tm.idle = true if tm.tasks.length == 0
            [fullPath, cb] = tm.tasks.shift()
            canvas = document.querySelector("canvas")
            c = canvas.getContext("2d")
            c.clearRect(0, 0, canvas.width, canvas.height)
            img = new Image()
            img.onload = (e)->
                thumbSize = 400
                if img.width > img.height
                    w = Math.min(img.width, thumbSize)
                    h = w * img.height / img.width
                else
                    h = Math.min(img.height, thumbSize)
                    w = h * img.width / img.height

                canvas.width = w
                canvas.height = h
                c.drawImage @, 0, 0, w, h
                cb(canvas.toDataURL "image/png")
                $timeout tm.nextTask, 10

            img.src = fullPath

        update: ->
            return if not ThumbMaker.idle
            ThumbMaker.idle = false
            ThumbMaker.nextTask()

        start: ->
            $rootScope.$watch (->ThumbMaker.tasks.length), ThumbMaker.update

    ThumbMaker.start()

    Compressor =
        compressing: false
        update: ->
            return if Compressor.compressing
            Compressor.compressing = true
            Compressor.compressNext()

        compressNext: ->
            m = searchModel((info)-> info['autoCompress'])
            if m is null
                Compressor.compressing = false
                return
            else
                console.log '[App FileStatusService compressNext] fullPath', m.fullPath
                m.progress = PROGRESS.WAITING
                m.autoCompress = false
                compressFile m.fullPath
                $timeout (-> Compressor.compressNext()), 10
                return

    init = ->
        wsManager.addConnectedHandler ->
            wsManager.ws.send wsManager.EVENT_REQ.COMPRESSED_COUNT().json

        $rootScope.$watch((-> return [model.length, (i.autoCompress for i in model)]), ((newValue, oldValue)->
            console.log '[App FileStatusService $watch] model.length', model.length
            Compressor.update()
        ), true)

    #        $rootScope.$watchCollection (-> return model), (newValue, oldValue)->
    #            console.log 'watch change', newValue, oldValue

    ret =
        PROGRESS: PROGRESS
        load: (fullPath) ->
            FileLoader.push fullPath

        limited: ->
            return not $rootScope.isVIP() and pushedCount >= 10

        pushedCount: -> return pushedCount

        zeroSize: (fullPath) ->
            return fs.lstatSync(fullPath).size == 0

        rightExt: (fullPath) ->
            return path.parse(fullPath.toLowerCase()).ext in ['.png', '.jpg', '.jpeg']

        makeFileInfo: (fullPath) ->
            pathStatus = fs.lstatSync(fullPath)
            retInfo =
                fullPath: fullPath
                originSize: pathStatus.size
                progress: ret.PROGRESS.NOT_START
                size: null
                pathInfo: path.parse(fullPath)
                birthTime: pathStatus.birthtime
                autoCompress: true
                isOrigin: true
                reduceRate: null
                compressingRate: 0
                backupFullPath: backupFullPath(fullPath)
                thumbSrc: null
                status:
                    isCompressible: -> retInfo.progress & (PROGRESS.NOT_START | PROGRESS.COMPRESS_ERROR | PROGRESS.ERROR_FORMAT | PROGRESS.COMPRESSED_LARGER)
                    isCompressed: -> retInfo.progress & PROGRESS.COMPRESSED
                    isFinished: -> retInfo.progress & (PROGRESS.COMPRESSED | PROGRESS.COMPRESSED_LARGER)
                    isCompressing: -> retInfo.progress & PROGRESS.COMPRESSING
                    isHandled: -> retInfo.progress & (PROGRESS.COMPRESSED | PROGRESS.COMPRESS_ERROR | PROGRESS.ERROR_FORMAT | PROGRESS.COMPRESSED_LARGER)

                setAutoCompress: (bool)->
                    retInfo.autoCompress = bool
                    $timeout (-> $rootScope.$apply()), 0

                restoreFile: ->
                    reader = fs.createReadStream(retInfo.backupFullPath)
                    reader.pipe(fs.createWriteStream(fullPath))
                    retInfo.restoreFileInfo()
                    return reader

                restoreFileInfo: ->
                    retInfo.size = null
                    retInfo.reduceRate = null
                    $timeout (-> $rootScope.$apply()), 0

                restoreToOrigin: ->
                    reader = retInfo.restoreFile()
                    retInfo.isOrigin = true
                    retInfo.progress = PROGRESS.NOT_START
                    return reader

                onCompressed: ->
                    m = retInfo
                    m.getSize()
                    m.getReduceRate()
                    m.isOrigin = false
                    if m.getReduceRate() == 0
                        m.restoreFile()
                        m.progress = PROGRESS.COMPRESSED_LARGER

                getSize: -> retInfo.size = fs.lstatSync(retInfo.fullPath).size
                getReduceRate: ->
                    retInfo.reduceRate = Math.max(0, 1 - retInfo.size / retInfo.originSize)

            imageSize fullPath, (err, bound)->
                retInfo.bound = bound
                $timeout (-> $rootScope.$apply()), 0

            ThumbMaker.push fullPath, (src)->
                retInfo.thumbSrc = src

            return retInfo


        model: ->
            return model

        remove: (info) ->
            model.splice(model.indexOf(info), 1)

        emptyModel: ->
            model = []
#            $timeout (->$rootScope.$apply()), 0

        existsModel: []

        testPush: (d)->
            pushModel(d)

    init()
    return ret


app.controller "TopController", ($rootScope, $scope, FileStatusService, Redirector, $timeout, $modal) ->
    $scope.onShowOpenDialog = ->
        showOpenDialog (fullPathList) ->
            FileStatusService.load(fullPath) for fullPath in fullPathList
            Redirector.go(Redirector.views.file)

    $scope.onCleanView = ->
        return if FileStatusService.model().length == 0
        FileStatusService.emptyModel() if confirm '是否清空列表'

    $scope.$watch (-> FileStatusService.pushedCount()), ->
        $scope.pushedCount = Math.max(0, 10 - FileStatusService.pushedCount())

    $scope.$watch (-> (i.size for i in FileStatusService.model()) ), (()->
        s = FileStatusService
        if s.model().length == 0
            $scope.modelEmpty = true
            Redirector.go(Redirector.views.drop)
            return

        Redirector.go(Redirector.views.file)
        $scope.modelEmpty = false
        compressed = (info for info in s.model() when info.progress == s.PROGRESS.COMPRESSED)
        handled = (info for info in s.model() when info.status.isHandled())
        $scope.compressedLength = compressed.length
        return if compressed.length == 0
        sumModel = (func) ->
            r = 0
            r += func(info) for info in compressed
            return r

        $scope.statistic =
            origin: sumModel ((info)-> info.originSize)
            size: sumModel ((info)-> info.size)
            fileCount: handled.length
            compressedCount: compressed.length
            totalCount: s.model().length

        $scope.statistic.compress = Math.round((1 - $scope.statistic.size / $scope.statistic.origin) * 100)
#        console.log '[TopController $watch model] $scope.statistic', $scope.statistic
    ), true

    $scope.modelEmpty = true

    $rootScope.openExternal = (url) ->
        window.openExternal(url)

    # notify
    $rootScope.notifyBox =
        allMsg: []
        post: (logo, message, linkSrc, linkText)->
            obj =
                logo: logo
                message: message
                linkSrc: linkSrc
                linkText: linkText
            $rootScope.notifyBox.allMsg.push obj
            $timeout (-> $rootScope.$apply()), 0

        start: ->
            $rootScope.$watch (-> $rootScope.notifyBox.allMsg.length), ->
                nb = $rootScope.notifyBox
                return if nb._working
                nb._working = true
                t = 2 * 1000
                showNext = ->
                    return nb._working = false if nb.allMsg.length == 0
                    msg = nb.allMsg.shift()
                    nb._show msg
                    $timeout showNext, t
                showNext()

        _working: false
        _show: (obj)->
            $rootScope.notify.showBox = false
            $rootScope.notify.logo = obj.logo
            $rootScope.notify.message = obj.message
            $rootScope.notify.linkSrc = obj.linkSrc
            $rootScope.notify.linkText = obj.linkText
            $timeout (->
                $rootScope.notify.showBox = true
            ), 10

    $rootScope.notify =
        showBox: false

    $rootScope.notifyBox.start()
    # -- notify


    # about modal
    $scope.aboutModal =
        redirectToLogin: false
        modal: $modal({scope: $scope, template: 'about.html', show: false})
        onShow: -> return
        onHide: -> return
        setOnShow: (func) -> @onShow = func
        setOnHide: (func) -> @onHide = func
        loginView: ->
            @redirectToLogin = true
            $scope.aboutModal.modal.show()

        aboutView: ->
            $scope.aboutModal.modal.show()

    $scope.$on 'modal.show', -> $scope.aboutModal.onShow()
    $scope.$on 'modal.hide', -> $scope.aboutModal.onHide()

    # -- about modal

    ## vip
    $rootScope._vipEmail = ""
    $rootScope.getEmail = -> return $rootScope._vipEmail
    $rootScope.setEmail = (email) -> return $rootScope._vipEmail = email
    $rootScope.isVIP = -> return $rootScope._vipEmail != ""

    window.wsManager.addMessageHandler (message) ->
        switch message.event
            when wsManager.EVENT_RES.CHECK_VIP
                if message.success
                    email = message.message
                    $rootScope.setEmail email
                else
                    $rootScope.setEmail ''
                $timeout (-> $rootScope.$apply()), 0

    window.wsManager.addConnectedHandler ->
        window.checkVIP()
    ## vip

    console.log '[TopController] init ok'

    $rootScope.version = '1.0.2'
    return
