/*
 * Copyright 2017 resin.io
 *
 * 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.
 */

'use strict'

const m = require('mochainon')
const _ = require('lodash')
const fs = require('fs')
const path = require('path')
const supportedFormats = require('../../../lib/shared/supported-formats')
const angular = require('angular')
const flashState = require('../../../lib/gui/app/models/flash-state')
const availableDrives = require('../../../lib/gui/app/models/available-drives')
const selectionState = require('../../../lib/gui/app/models/selection-state')
require('angular-mocks')

// Mock HTML requires by reading from the file-system
// eslint-disable-next-line node/no-deprecated-api
require.extensions['.html'] = (module, filename) => {
  module.exports = fs.readFileSync(filename, {
    encoding: 'utf8'
  })
}

// NOTE(Shou): since we don't test React yet we just ignore JSX files
// eslint-disable-next-line node/no-deprecated-api
require.extensions['.jsx'] = _.constant(null)

describe('Browser: MainPage', function () {
  beforeEach(angular.mock.module(
    require('../../../lib/gui/app/pages/main/main')
  ))

  describe('MainController', function () {
    let $controller

    beforeEach(angular.mock.inject(function (_$controller_) {
      $controller = _$controller_
    }))

    describe('.shouldDriveStepBeDisabled()', function () {
      it('should return true if there is no drive', function () {
        const controller = $controller('MainController', {
          $scope: {}
        })

        selectionState.clear()

        m.chai.expect(controller.shouldDriveStepBeDisabled()).to.be.true
      })

      it('should return false if there is a drive', function () {
        const controller = $controller('MainController', {
          $scope: {}
        })

        selectionState.selectImage({
          path: 'rpi.img',
          extension: 'img',
          size: 99999,
          isSizeEstimated: false
        })

        m.chai.expect(controller.shouldDriveStepBeDisabled()).to.be.false
      })
    })

    describe('.shouldFlashStepBeDisabled()', function () {
      it('should return true if there is no selected drive nor image', function () {
        const controller = $controller('MainController', {
          $scope: {}
        })

        selectionState.clear()

        m.chai.expect(controller.shouldFlashStepBeDisabled()).to.be.true
      })

      it('should return true if there is a selected image but no drive', function () {
        const controller = $controller('MainController', {
          $scope: {}
        })

        selectionState.clear()
        selectionState.selectImage({
          path: 'rpi.img',
          extension: 'img',
          size: 99999,
          isSizeEstimated: false
        })

        m.chai.expect(controller.shouldFlashStepBeDisabled()).to.be.true
      })

      it('should return true if there is a selected drive but no image', function () {
        const controller = $controller('MainController', {
          $scope: {}
        })

        availableDrives.setDrives([
          {
            device: '/dev/disk2',
            description: 'Foo',
            size: 99999,
            mountpoint: '/mnt/foo',
            system: false
          }
        ])

        selectionState.clear()
        selectionState.selectDrive('/dev/disk2')

        m.chai.expect(controller.shouldFlashStepBeDisabled()).to.be.true
      })

      it('should return false if there is a selected drive and a selected image', function () {
        const controller = $controller('MainController', {
          $scope: {}
        })

        availableDrives.setDrives([
          {
            device: '/dev/disk2',
            description: 'Foo',
            size: 99999,
            mountpoint: '/mnt/foo',
            system: false
          }
        ])

        selectionState.clear()
        selectionState.selectDrive('/dev/disk2')

        selectionState.selectImage({
          path: 'rpi.img',
          extension: 'img',
          size: 99999,
          isSizeEstimated: false
        })

        m.chai.expect(controller.shouldFlashStepBeDisabled()).to.be.false
      })
    })
  })

  describe('ImageSelectionController', function () {
    let $controller

    beforeEach(angular.mock.inject(function (_$controller_) {
      $controller = _$controller_
    }))

    it('should contain all available extensions in mainSupportedExtensions and extraSupportedExtensions', function () {
      const $scope = {}
      const controller = $controller('ImageSelectionController', {
        $scope
      })

      const extensions = controller.mainSupportedExtensions.concat(controller.extraSupportedExtensions)
      m.chai.expect(_.sortBy(extensions)).to.deep.equal(_.sortBy(supportedFormats.getAllExtensions()))
    })

    describe('.getImageBasename()', function () {
      it('should return the basename of the selected image', function () {
        const controller = $controller('ImageSelectionController', {
          $scope: {}
        })

        selectionState.selectImage({
          path: path.join(__dirname, 'foo', 'bar.img'),
          extension: 'img',
          size: 999999999,
          isSizeEstimated: false
        })

        m.chai.expect(controller.getImageBasename()).to.equal('bar.img')
        selectionState.deselectImage()
      })

      it('should return an empty string if no selected image', function () {
        const controller = $controller('ImageSelectionController', {
          $scope: {}
        })

        selectionState.deselectImage()
        m.chai.expect(controller.getImageBasename()).to.equal('')
      })
    })
  })

  describe('FlashController', function () {
    let $controller

    beforeEach(angular.mock.inject(function (_$controller_) {
      $controller = _$controller_
    }))

    describe('.getProgressButtonLabel()', function () {
      it('should return "Flash!" given a clean state', function () {
        const controller = $controller('FlashController', {
          $scope: {}
        })

        flashState.resetState()
        m.chai.expect(controller.getProgressButtonLabel()).to.equal('Flash!')
      })

      it('should display the flashing progress', function () {
        const controller = $controller('FlashController', {
          $scope: {}
        })

        flashState.setFlashingFlag()
        flashState.setProgressState({
          flashing: 1,
          verifying: 0,
          successful: 0,
          failed: 0,
          percentage: 85,
          eta: 15,
          speed: 1000,
          totalSpeed: 2000
        })
        m.chai.expect(controller.getProgressButtonLabel()).to.equal('85% Flashing')
      })
    })
  })

  describe('DriveSelectionController', function () {
    let $controller
    let DriveSelectionController

    const drivePaths = process.platform === 'win32'
      ? [ '\\\\.\\PhysicalDrive1', '\\\\.\\PhysicalDrive2', '\\\\.\\PhysicalDrive3' ]
      : [ '/dev/disk1', '/dev/disk2', '/dev/disk3' ]
    const drives = [
      {
        device: drivePaths[0],
        description: 'My Drive',
        size: 123456789,
        displayName: drivePaths[0],
        mountpoints: [ drivePaths[0] ],
        isSystem: false,
        isReadOnly: false
      },
      {
        device: drivePaths[1],
        description: 'My Other Drive',
        size: 987654321,
        displayName: drivePaths[1],
        mountpoints: [ drivePaths[1] ],
        isSystem: false,
        isReadOnly: false
      },
      {
        device: drivePaths[2],
        size: 987654321,
        displayName: drivePaths[2],
        mountpoints: [],
        isSystem: false,
        isReadOnly: false
      }
    ]

    beforeEach(angular.mock.inject(function (_$controller_) {
      $controller = _$controller_
      DriveSelectionController = $controller('DriveSelectionController', {
        $scope: {}
      })

      availableDrives.setDrives(drives)
    }))

    afterEach(() => {
      selectionState.clear()
    })

    describe('.getDrivesTitle()', function () {
      it('should return the drive description when there is one drive', function () {
        selectionState.selectDrive(drives[0].device)
        m.chai.expect(DriveSelectionController.getDrivesTitle()).to.equal(drives[0].description)
      })

      it('should return untitled when there is no description', function () {
        selectionState.selectDrive(drives[2].device)
        m.chai.expect(DriveSelectionController.getDrivesTitle()).to.equal('Untitled Device')
      })

      it('should return a consolidated title with quantity when there are multiple drives', function () {
        selectionState.selectDrive(drives[0].device)
        selectionState.selectDrive(drives[1].device)
        m.chai.expect(DriveSelectionController.getDrivesTitle()).to.equal('2 Devices')
      })
    })

    describe('.getDriveListLabel()', function () {
      it('should return the drive description and display name when there is one drive', function () {
        const label = `${drives[0].description} (${drives[0].displayName})`
        selectionState.selectDrive(drives[0].device)
        m.chai.expect(DriveSelectionController.getDriveListLabel()).to.equal(label)
      })

      it('should return drive descriptions and display names of all drives separated by newlines', function () {
        const label = `${drives[0].description} (${drives[0].displayName})\n${drives[1].description} (${drives[1].displayName})`
        selectionState.selectDrive(drives[0].device)
        selectionState.selectDrive(drives[1].device)
        m.chai.expect(DriveSelectionController.getDriveListLabel()).to.equal(label)
      })
    })
  })

  describe('page template', function () {
    let $state

    beforeEach(angular.mock.inject(function (_$state_) {
      $state = _$state_
    }))

    it('should match the file contents', function () {
      const {
        template
      } = $state.get('main')
      const contents = fs.readFileSync('lib/gui/app/pages/main/templates/main.tpl.html', {
        encoding: 'utf-8'
      })
      m.chai.expect(template).to.equal(contents)
    })
  })
})
