/*
 * Minio Cloud Storage (C) 2016 Minio, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import Moment from 'moment'
import browserHistory from 'react-router/lib/browserHistory'
import storage from 'local-storage-fallback'
import { minioBrowserPrefix } from './constants'

export const SET_WEB = 'SET_WEB'
export const SET_CURRENT_BUCKET = 'SET_CURRENT_BUCKET'
export const SET_CURRENT_PATH = 'SET_CURRENT_PATH'
export const SET_BUCKETS = 'SET_BUCKETS'
export const ADD_BUCKET = 'ADD_BUCKET'
export const SET_VISIBLE_BUCKETS = 'SET_VISIBLE_BUCKETS'
export const SET_OBJECTS = 'SET_OBJECTS'
export const APPEND_OBJECTS = 'APPEND_OBJECTS'
export const RESET_OBJECTS = 'RESET_OBJECTS'
export const SET_STORAGE_INFO = 'SET_STORAGE_INFO'
export const SET_SERVER_INFO = 'SET_SERVER_INFO'
export const SHOW_MAKEBUCKET_MODAL = 'SHOW_MAKEBUCKET_MODAL'
export const ADD_UPLOAD = 'ADD_UPLOAD'
export const STOP_UPLOAD = 'STOP_UPLOAD'
export const UPLOAD_PROGRESS = 'UPLOAD_PROGRESS'
export const SET_ALERT = 'SET_ALERT'
export const SET_LOGIN_ERROR = 'SET_LOGIN_ERROR'
export const SET_SHOW_ABORT_MODAL = 'SET_SHOW_ABORT_MODAL'
export const SHOW_ABOUT = 'SHOW_ABOUT'
export const SET_SORT_NAME_ORDER = 'SET_SORT_NAME_ORDER'
export const SET_SORT_SIZE_ORDER = 'SET_SORT_SIZE_ORDER'
export const SET_SORT_DATE_ORDER = 'SET_SORT_DATE_ORDER'
export const SET_LATEST_UI_VERSION = 'SET_LATEST_UI_VERSION'
export const SET_SIDEBAR_STATUS = 'SET_SIDEBAR_STATUS'
export const SET_LOGIN_REDIRECT_PATH = 'SET_LOGIN_REDIRECT_PATH'
export const SET_LOAD_BUCKET = 'SET_LOAD_BUCKET'
export const SET_LOAD_PATH = 'SET_LOAD_PATH'
export const SHOW_SETTINGS = 'SHOW_SETTINGS'
export const SET_SETTINGS = 'SET_SETTINGS'
export const SHOW_BUCKET_POLICY = 'SHOW_BUCKET_POLICY'
export const SET_POLICIES = 'SET_POLICIES'
export const SET_SHARE_OBJECT = 'SET_SHARE_OBJECT'
export const DELETE_CONFIRMATION = 'DELETE_CONFIRMATION'
export const SET_PREFIX_WRITABLE = 'SET_PREFIX_WRITABLE'
export const REMOVE_OBJECT = 'REMOVE_OBJECT'
export const CHECKED_OBJECTS_ADD = 'CHECKED_OBJECTS_ADD'
export const CHECKED_OBJECTS_REMOVE = 'CHECKED_OBJECTS_REMOVE'
export const CHECKED_OBJECTS_RESET = 'CHECKED_OBJECTS_RESET'

export const showDeleteConfirmation = (object) => {
  return {
    type: DELETE_CONFIRMATION,
    payload: {
      object,
      show: true
    }
  }
}

export const hideDeleteConfirmation = () => {
  return {
    type: DELETE_CONFIRMATION,
    payload: {
      object: '',
      show: false
    }
  }
}

export const showShareObject = (object, url) => {
  return {
    type: SET_SHARE_OBJECT,
    shareObject: {
      object,
      url,
      show: true
    }
  }
}

export const hideShareObject = () => {
  return {
    type: SET_SHARE_OBJECT,
    shareObject: {
      url: '',
      show: false
    }
  }
}

export const shareObject = (object, days, hours, minutes) => (dispatch, getState) => {
  const {currentBucket, web} = getState()
  let host = location.host
  let bucket = currentBucket

  if (!web.LoggedIn()) {
    dispatch(showShareObject(object, `${host}/${bucket}/${object}`))
    return
  }

  let expiry = days * 24 * 60 * 60 + hours * 60 * 60 + minutes * 60
  web.PresignedGet({
    host,
    bucket,
    object,
    expiry
  })
    .then(obj => {
      dispatch(showShareObject(object, obj.url))
      dispatch(showAlert({
        type: 'success',
        message: `Object shared. Expires in ${days} days ${hours} hours ${minutes} minutes.`
      }))
    })
    .catch(err => {
      dispatch(showAlert({
        type: 'danger',
        message: err.message
      }))
    })
}

export const setLoginRedirectPath = (path) => {
  return {
    type: SET_LOGIN_REDIRECT_PATH,
    path
  }
}

export const setLoadPath = (loadPath) => {
  return {
    type: SET_LOAD_PATH,
    loadPath
  }
}

export const setLoadBucket = (loadBucket) => {
  return {
    type: SET_LOAD_BUCKET,
    loadBucket
  }
}

export const setWeb = web => {
  return {
    type: SET_WEB,
    web
  }
}

export const setBuckets = buckets => {
  return {
    type: SET_BUCKETS,
    buckets
  }
}

export const addBucket = bucket => {
  return {
    type: ADD_BUCKET,
    bucket
  }
}

export const showMakeBucketModal = () => {
  return {
    type: SHOW_MAKEBUCKET_MODAL,
    showMakeBucketModal: true
  }
}

export const hideAlert = () => {
  return {
    type: SET_ALERT,
    alert: {
      show: false,
      message: '',
      type: ''
    }
  }
}

export const showAlert = alert => {
  return (dispatch, getState) => {
    let alertTimeout = null
    if (alert.type !== 'danger') {
      alertTimeout = setTimeout(() => {
        dispatch({
          type: SET_ALERT,
          alert: {
            show: false
          }
        })
      }, 5000)
    }
    dispatch({
      type: SET_ALERT,
      alert: Object.assign({}, alert, {
        show: true,
        alertTimeout
      })
    })
  }
}

export const removeObject = object => {
  return {
    type: REMOVE_OBJECT,
    object
  }
}

export const setSidebarStatus = (status) => {
  return {
    type: SET_SIDEBAR_STATUS,
    sidebarStatus: status
  }
}

export const hideMakeBucketModal = () => {
  return {
    type: SHOW_MAKEBUCKET_MODAL,
    showMakeBucketModal: false
  }
}

export const setVisibleBuckets = visibleBuckets => {
  return {
    type: SET_VISIBLE_BUCKETS,
    visibleBuckets
  }
}

const appendObjects = (objects, marker, istruncated) => {
  return {
    type: APPEND_OBJECTS,
    objects,
    marker,
    istruncated
  }
}

export const setObjects = (objects) => {
  return {
    type: SET_OBJECTS,
    objects,
  }
}

export const resetObjects = () => {
  return {
    type: RESET_OBJECTS
  }
}

export const setCurrentBucket = currentBucket => {
  return {
    type: SET_CURRENT_BUCKET,
    currentBucket
  }
}

export const setCurrentPath = currentPath => {
  return {
    type: SET_CURRENT_PATH,
    currentPath
  }
}

export const setStorageInfo = storageInfo => {
  return {
    type: SET_STORAGE_INFO,
    storageInfo
  }
}

export const setServerInfo = serverInfo => {
  return {
    type: SET_SERVER_INFO,
    serverInfo
  }
}

const setPrefixWritable = prefixWritable => {
  return {
    type: SET_PREFIX_WRITABLE,
    prefixWritable,
  }
}

export const selectBucket = (newCurrentBucket, prefix) => {
  if (!prefix)
    prefix = ''
  return (dispatch, getState) => {
    let web = getState().web
    let currentBucket = getState().currentBucket

    if (currentBucket !== newCurrentBucket) dispatch(setLoadBucket(newCurrentBucket))

    dispatch(setCurrentBucket(newCurrentBucket))
    dispatch(selectPrefix(prefix))
    return
  }
}

export const listObjects = () => {
  return (dispatch, getState) => {
    const {currentBucket, currentPath, marker, objects, istruncated, web} = getState()
    if (!istruncated) return
    web.ListObjects({
      bucketName: currentBucket,
      prefix: currentPath,
      marker: marker
    })
      .then(res => {
        let objects = res.objects
        if (!objects)
          objects = []
        objects = objects.map(object => {
          object.name = object.name.replace(`${currentPath}`, '');
          return object
        })
        dispatch(appendObjects(objects, res.nextmarker, res.istruncated))
        dispatch(setPrefixWritable(res.writable))
        dispatch(setLoadBucket(''))
        dispatch(setLoadPath(''))
      })
      .catch(err => {
        dispatch(showAlert({
          type: 'danger',
          message: err.message
        }))
        dispatch(setLoadBucket(''))
        dispatch(setLoadPath(''))
        // Use browserHistory.replace instead of push so that browser back button works fine.
        browserHistory.replace(`${minioBrowserPrefix}/login`)
      })
  }
}

export const selectPrefix = prefix => {
  return (dispatch, getState) => {
    const {currentBucket, web} = getState()
    dispatch(resetObjects())
    dispatch(setLoadPath(prefix))
    web.ListObjects({
      bucketName: currentBucket,
      prefix,
      marker: ""
    })
      .then(res => {
        let objects = res.objects
        if (!objects)
          objects = []
        objects = objects.map(object => {
          object.name = object.name.replace(`${prefix}`, '');
          return object
        })
        dispatch(appendObjects(
          objects,
          res.nextmarker,
          res.istruncated
        ))
        dispatch(setPrefixWritable(res.writable))
        dispatch(setSortNameOrder(false))
        dispatch(setCurrentPath(prefix))
        dispatch(setLoadBucket(''))
        dispatch(setLoadPath(''))
      })
      .catch(err => {
        dispatch(showAlert({
          type: 'danger',
          message: err.message
        }))
        dispatch(setLoadBucket(''))
        dispatch(setLoadPath(''))
        // Use browserHistory.replace instead of push so that browser back button works fine.
        browserHistory.replace(`${minioBrowserPrefix}/login`)
      })
  }
}

export const addUpload = options => {
  return {
    type: ADD_UPLOAD,
    slug: options.slug,
    size: options.size,
    xhr: options.xhr,
    name: options.name
  }
}

export const stopUpload = options => {
  return {
    type: STOP_UPLOAD,
    slug: options.slug
  }
}

export const uploadProgress = options => {
  return {
    type: UPLOAD_PROGRESS,
    slug: options.slug,
    loaded: options.loaded
  }
}

export const setShowAbortModal = showAbortModal => {
  return {
    type: SET_SHOW_ABORT_MODAL,
    showAbortModal
  }
}

export const setLoginError = () => {
  return {
    type: SET_LOGIN_ERROR,
    loginError: true
  }
}

export const downloadSelected = (url, req, xhr) => {
  return (dispatch) => {
    var anchor = document.createElement('a')
    document.body.appendChild(anchor);
    xhr.open('POST', url, true)
    xhr.responseType = 'blob'

    xhr.onload = function(e) {
      if (this.status == 200) {
        dispatch(checkedObjectsReset())
        var blob = new Blob([this.response], {
          type: 'octet/stream'
        })
        var blobUrl = window.URL.createObjectURL(blob);
        var separator = req.prefix.length > 1 ? '-' : ''

        anchor.href = blobUrl
        anchor.download = req.bucketName+separator+req.prefix.slice(0, -1)+'.zip';




        anchor.click()
        window.URL.revokeObjectURL(blobUrl)
        anchor.remove()
      }
    };
    xhr.send(JSON.stringify(req));
  }
}

export const uploadFile = (file, xhr) => {
  return (dispatch, getState) => {
    const {currentBucket, currentPath} = getState()
    const objectName = `${currentPath}${file.name}`
    const uploadUrl = `${window.location.origin}/minio/upload/${currentBucket}/${objectName}`
    // The slug is a unique identifer for the file upload.
    const slug = `${currentBucket}-${currentPath}-${file.name}`

    xhr.open('PUT', uploadUrl, true)
    xhr.withCredentials = false
    const token = storage.getItem('token')
    if (token) xhr.setRequestHeader("Authorization", 'Bearer ' + storage.getItem('token'))
    xhr.setRequestHeader('x-amz-date', Moment().utc().format('YYYYMMDDTHHmmss') + 'Z')
    dispatch(addUpload({
      slug,
      xhr,
      size: file.size,
      name: file.name
    }))

    xhr.onload = function(event) {
      if (xhr.status == 401 || xhr.status == 403 || xhr.status == 500) {
        setShowAbortModal(false)
        dispatch(stopUpload({
          slug
        }))
        dispatch(showAlert({
          type: 'danger',
          message: 'Unauthorized request.'
        }))
      }
      if (xhr.status == 200) {
        setShowAbortModal(false)
        dispatch(stopUpload({
          slug
        }))
        dispatch(showAlert({
          type: 'success',
          message: 'File \'' + file.name + '\' uploaded successfully.'
        }))
        dispatch(selectPrefix(currentPath))
      }
    }

    xhr.upload.addEventListener('error', event => {
      dispatch(showAlert({
        type: 'danger',
        message: 'Error occurred uploading \'' + file.name + '\'.'
      }))
      dispatch(stopUpload({
        slug
      }))
    })

    xhr.upload.addEventListener('progress', event => {
      if (event.lengthComputable) {
        let loaded = event.loaded
        let total = event.total

        // Update the counter.
        dispatch(uploadProgress({
          slug,
          loaded
        }))
      }
    })
    xhr.send(file)
  }
}

export const showAbout = () => {
  return {
    type: SHOW_ABOUT,
    showAbout: true
  }
}

export const hideAbout = () => {
  return {
    type: SHOW_ABOUT,
    showAbout: false
  }
}

export const setSortNameOrder = (sortNameOrder) => {
  return {
    type: SET_SORT_NAME_ORDER,
    sortNameOrder
  }
}

export const setSortSizeOrder = (sortSizeOrder) => {
  return {
    type: SET_SORT_SIZE_ORDER,
    sortSizeOrder
  }
}

export const setSortDateOrder = (sortDateOrder) => {
  return {
    type: SET_SORT_DATE_ORDER,
    sortDateOrder
  }
}

export const setLatestUIVersion = (latestUiVersion) => {
  return {
    type: SET_LATEST_UI_VERSION,
    latestUiVersion
  }
}

export const showSettings = () => {
  return {
    type: SHOW_SETTINGS,
    showSettings: true
  }
}

export const hideSettings = () => {
  return {
    type: SHOW_SETTINGS,
    showSettings: false
  }
}

export const setSettings = (settings) => {
  return {
    type: SET_SETTINGS,
    settings
  }
}

export const showBucketPolicy = () => {
  return {
    type: SHOW_BUCKET_POLICY,
    showBucketPolicy: true
  }
}

export const hideBucketPolicy = () => {
  return {
    type: SHOW_BUCKET_POLICY,
    showBucketPolicy: false
  }
}

export const setPolicies = (policies) => {
  return {
    type: SET_POLICIES,
    policies
  }
}

export const checkedObjectsAdd = (objectName) => {
  return {
    type: CHECKED_OBJECTS_ADD,
    objectName
  }
}

export const checkedObjectsRemove = (objectName) => {
  return {
    type: CHECKED_OBJECTS_REMOVE,
    objectName
  }
}

export const checkedObjectsReset = (objectName) => {
  return {
    type: CHECKED_OBJECTS_RESET,
    objectName
  }
}
