/*!
 * The MIT License (MIT)
 *
 * Copyright (c) 2019 Mark van Seventer
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

// Strict mode.
'use strict'

// Package modules.
const yargs = require('yargs')

// Local modules.
const constants = require('./constants')
const pkg = require('../package.json')
const queue = require('./queue')

// Configure.
const IS_TEXT_TERMINAL = process.stdin.isTTY

// Options.
const _global = 'Global Options'
const optimize = 'Optimization Options'
const options = {
  // Global options.

  // @see https://sharp.pixelplumbing.com/en/stable/api-output/#png
  compressionLevel: {
    alias: 'c',
    desc: 'zlib compression level',
    defaultDescription: 9,
    group: _global,
    nargs: 1,
    type: 'number'
  },

  // @see https://sharp.pixelplumbing.com/en/stable/api-output/#toformat
  format: {
    alias: 'f',
    choices: [ 'input', ...constants.FORMAT ],
    default: 'input',
    desc: 'Force output to a given format',
    group: _global,
    nargs: 1
  },

  // @see https://sharp.pixelplumbing.com/en/stable/api-constructor/
  input: {
    alias: 'i',
    defaultDescription: 'stdin',
    demand: IS_TEXT_TERMINAL,
    desc: 'Path to (an) image file(s)',
    group: _global,
    implies: 'output',
    normalize: true,
    type: 'array'
  },

  // @see https://sharp.pixelplumbing.com/en/stable/api-input/#limitinputpixels
  limitInputPixels: {
    alias: 'l',
    defaultDescription: 0x3FFF * 0x3FFF,
    desc: 'Do not process input images where the number of pixels (width x height) exceeds this limit',
    group: _global,
    nargs: 1,
    type: 'number'
  },

  // @see https://sharp.pixelplumbing.com/en/stable/api-output/
  output: {
    alias: 'o',
    defaultDescription: 'stdout',
    demand: IS_TEXT_TERMINAL,
    desc: 'Directory or URI template to write the image files to',
    group: _global,
    nargs: 1,
    normalize: true,
    type: 'string'
  },

  // @see https://sharp.pixelplumbing.com/en/stable/api-output/#jpeg
  // @see https://sharp.pixelplumbing.com/en/stable/api-output/#png
  progressive: {
    alias: 'p',
    desc: 'Use progressive (interlace) scan',
    group: _global,
    type: 'boolean'
  },

  // @see https://sharp.pixelplumbing.com/en/stable/api-output/#jpeg
  // @see https://sharp.pixelplumbing.com/en/stable/api-output/#tiff
  // @see https://sharp.pixelplumbing.com/en/stable/api-output/#webp
  quality: {
    alias: 'q',
    desc: 'Quality',
    defaultDescription: '80',
    group: _global,
    nargs: 1,
    type: 'number'
  },

  // @see https://sharp.pixelplumbing.com/en/stable/api-output/#withmetadata
  withMetadata: {
    alias: 'm',
    desc: 'Include all metadata (EXIF, XMP, IPTC) from the input image in the output image',
    global: true,
    group: _global,
    type: 'boolean'
  },

  // Optimization options.

  // @see https://sharp.pixelplumbing.com/en/stable/api-output/#png
  adaptiveFiltering: {
    desc: 'Use adaptive row filtering',
    group: optimize,
    type: 'boolean'
  },

  // @see https://sharp.pixelplumbing.com/en/stable/api-output/#webp
  alphaQuality: {
    desc: 'Quality of alpha layer',
    defaultDescription: '80',
    group: optimize,
    nargs: 1,
    type: 'number'
  },

  // @see https://sharp.pixelplumbing.com/en/stable/api-output/#jpeg
  chromaSubsampling: {
    desc: 'Set to "4:4:4" to prevent chroma subsampling when quality <= 90',
    defaultDescription: '4:2:0',
    group: optimize,
    nargs: 1,
    type: 'string'
  },

  // @see https://sharp.dimens.io/en/stable/api-output/#png
  colors: {
    alias: 'colours',
    defaultDescription: 256,
    desc: 'Maximum number of palette entries',
    group: optimize,
    nargs: 1,
    type: 'number'
  },

  // @see https://sharp.pixelplumbing.com/en/stable/api-output/#tiff
  compression: {
    choices: constants.TIFF_COMPRESSION,
    default: 'jpeg',
    desc: 'Compression options',
    group: optimize,
    nargs: 1,
    type: 'string'
  },

  // @see https://sharp.dimens.io/en/stable/api-output/#png
  dither: {
    desc: 'Level of Floyd-Steinberg error diffusion',
    defaultDescription: '1.0',
    group: optimize,
    nargs: 1,
    type: 'number'
  },

  // @see https://sharp.pixelplumbing.com/en/stable/api-output/#webp
  lossless: {
    desc: 'Use lossless compression mode',
    group: optimize,
    type: 'boolean'
  },

  // @see https://sharp.pixelplumbing.com/en/stable/api-output/#webp
  nearLossless: {
    desc: 'use near_lossless compression mode',
    group: optimize,
    type: 'boolean'
  },

  // @see https://sharp.pixelplumbing.com/en/stable/api-output/#jpeg
  optimise: {
    alias: 'optimize',
    desc: 'Apply optimiseScans, overshootDeringing, and trellisQuantisation',
    group: optimize,
    type: 'boolean'
  },

  // @see https://sharp.pixelplumbing.com/en/stable/api-output/#jpeg
  optimiseCoding: {
    alias: 'optimizeCoding',
    default: true,
    desc: 'Optimise Huffman coding tables',
    group: optimize,
    type: 'boolean'
  },

  // @see https://sharp.pixelplumbing.com/en/stable/api-output/#jpeg
  optimiseScans: {
    alias: 'optimizeScans',
    desc: 'Optimise progressive scans',
    group: optimize,
    implies: 'progressive',
    type: 'boolean'
  },

  // @see https://sharp.pixelplumbing.com/en/stable/api-output/#jpeg
  overshootDeringing: {
    desc: 'Apply overshoot deringing',
    group: optimize,
    type: 'boolean'
  },

  // @see https://sharp.dimens.io/en/stable/api-output/#png
  palette: {
    desc: 'Quantise to a palette-based image with alpha transparency support',
    group: optimize,
    type: 'boolean'
  },

  // @see https://sharp.pixelplumbing.com/en/stable/api-output/#tiff
  predictor: {
    choices: constants.TIFF_PREDICTOR,
    default: 'horizontal',
    desc: 'Compression predictor',
    group: optimize,
    nargs: 1,
    type: 'string'
  },

  // @see https://sharp.pixelplumbing.com/en/stable/api-output/#tiff
  pyramid: {
    desc: 'Write an image pyramid',
    group: optimize,
    type: 'boolean'
  },

  // @see https://sharp.pixelplumbing.com/en/stable/api-output/#jpeg
  quantisationTable: {
    alias: 'quantizationTable',
    defaultDescription: '0',
    desc: 'Quantization table to use',
    group: optimize,
    nargs: 1,
    type: 'number'
  },

  // @see https://sharp.pixelplumbing.com/en/stable/api-input/#sequentialread
  sequentialRead: {
    desc: 'An advanced setting that switches the libvips access method to VIPS_ACCESS_SEQUENTIAL',
    group: optimize,
    type: 'boolean'
  },

  // @see https://sharp.pixelplumbing.com/en/stable/api-output/#tiff
  squash: {
    desc: 'Squash 8-bit images down to 1 bit',
    group: optimize,
    type: 'boolean'
  },

  // @see https://sharp.pixelplumbing.com/en/stable/api-output/#tiff
  tileHeight: {
    desc: 'Vertical tile size',
    group: optimize,
    type: 'number'
  },

  // @see https://sharp.pixelplumbing.com/en/stable/api-output/#tiff
  tileWidth: {
    desc: 'Horizontal tile size',
    group: optimize,
    type: 'number'
  },

  // @see https://sharp.pixelplumbing.com/en/stable/api-output/#jpeg
  trellisQuantisation: {
    desc: 'Apply trellis quantisation',
    group: optimize,
    type: 'boolean'
  },

  // @see https://sharp.pixelplumbing.com/en/stable/api-output/#tiff
  xres: {
    defaultDescription: '1.0',
    desc: 'Horizontal resolution',
    group: optimize,
    type: 'number'
  },

  // @see https://sharp.pixelplumbing.com/en/stable/api-output/#tiff
  yres: {
    defaultDescription: '1.0',
    desc: 'Vertical resolution',
    group: optimize,
    type: 'number'
  }
}

// Configure.
const cli = yargs
  .strict()
  .usage('$0 <options> [command..]')
  .options(options)
  .example('$0 -i ./input.jpg -o ./out resize 300 200', 'out/input.jpg will be a 300 pixels wide and 200 pixels high image containing a scaled and cropped version of input.jpg')
  .example('$0 -i ./input.jpg -o ./out -mq90 rotate 180 -- resize 300 -- flatten "#ff6600" -- overlayWith ./overlay.png --gravity southeast -- sharpen', 'out/input.jpg will be an upside down, 300px wide, alpha channel flattened onto orange background, composited with overlay.png with SE gravity, sharpened, with metadata, 90% quality version of input.jpg')
  .epilog('For more information on available options, please visit https://sharp.pixelplumbing.com/')
  .showHelpOnFail(false)

  // Built-in options.
  .help().alias('help', 'h')
  .version(pkg.version).alias('version', 'v')
  .group([ 'help', 'version' ], 'Misc. Options')

  // Commands.
  // Avoid `yargs.commandDir()` as it uses insertion order, not alphabetical.
  .command(require('../cmd/channel-manipulation/bandbool'))
  .command(require('../cmd/operations/blur'))
  .command(require('../cmd/operations/boolean'))
  .command(require('../cmd/compositing/composite'))
  .command(require('../cmd/operations/convolve'))
  .command(require('../cmd/channel-manipulation/ensure-alpha'))
  .command(require('../cmd/resizing/extend'))
  .command(require('../cmd/resizing/extract'))
  .command(require('../cmd/channel-manipulation/extract'))
  .command(require('../cmd/operations/flatten'))
  .command(require('../cmd/operations/flip'))
  .command(require('../cmd/operations/flop'))
  .command(require('../cmd/operations/gamma'))
  .command(require('../cmd/colour-manipulation/greyscale'))
  .command(require('../cmd/channel-manipulation/join'))
  .command(require('../cmd/operations/linear'))
  .command(require('../cmd/operations/median'))
  .command(require('../cmd/operations/modulate'))
  .command(require('../cmd/operations/negate'))
  .command(require('../cmd/operations/normalise'))
  .command(require('../cmd/compositing/overlay-with'))
  .command(require('../cmd/operations/recomb'))
  .command(require('../cmd/channel-manipulation/remove-alpha'))
  .command(require('../cmd/resizing/resize'))
  .command(require('../cmd/operations/rotate'))
  .command(require('../cmd/operations/sharpen'))
  .command(require('../cmd/operations/threshold'))
  .command(require('../cmd/colour-manipulation/tint'))
  .command(require('../cmd/output'))
  .command(require('../cmd/colour-manipulation/tocolourspace'))
  .command(require('../cmd/resizing/trim'))

// Override `cli.parse` to handle global options.
const originalParse = cli.parse
cli.parse = (argv, context, callback) => {
  // Context is optional.
  if (typeof context === 'function') {
    callback = context
    context = { }
  }

  // Parse and return the arguments.
  return originalParse(argv, context, (err, args, output) => {
    // Handle arguments.
    // NOTE Use queue.unshift to apply global options first.
    if (args !== null) {
      // Require at least one input file.
      // NOTE: check here b/c https://github.com/yargs/yargs/issues/403
      if (args.input && args.input.length === 0) {
        err = new Error('Not enough arguments following: i, input')
      }

      // Global Options.

      // @see https://sharp.pixelplumbing.com/en/stable/api-output/#toformat
      if (args.format !== options.format.default) {
        queue.unshift([ 'format', (sharp) => sharp.toFormat(args.format) ])
      }

      // @see https://sharp.pixelplumbing.com/en/stable/api-output/#jpeg
      if (
        args.chromaSubsampling ||
        args.optimise ||
        args.optimiseCoding !== true ||
        args.optimiseScans ||
        args.overshootDeringing ||
        args.progressive ||
        args.quantisationTable ||
        args.quality ||
        args.trellisQuantisation
      ) {
        queue.unshift([ 'jpeg', (sharp) => {
          return sharp.jpeg({
            chromaSubsampling: args.chromaSubsampling,
            force: false,
            optimiseCoding: args.optimiseCoding,
            optimiseScans: args.optimise || args.optimiseScans,
            overshootDeringing: args.optimise || args.overshootDeringing,
            progressive: args.progressive,
            quality: args.quality,
            quantisationTable: args.quantisationTable,
            trellisQuantisation: args.optimise || args.trellisQuantisation
          })
        }])
      }

      // @see https://sharp.pixelplumbing.com/en/stable/api-input/#limitinputpixels
      if (undefined !== args.limitInputPixels) {
        queue.unshift([ 'limitInputPixels', (sharp) => sharp.limitInputPixels(args.limitInputPixels) ])
      }

      // @see https://sharp.pixelplumbing.com/en/stable/api-output/#png
      if (args.adaptiveFiltering || args.colors || args.compressionLevel ||
       args.dither || args.palette || args.progressive) {
        queue.unshift([ 'png', (sharp) => {
          return sharp.png({
            adaptiveFiltering: args.adaptiveFiltering,
            colors: args.colors,
            compressionLevel: args.compressionLevel,
            dither: args.dither,
            force: false,
            palette: args.palette,
            progressive: args.progressive
          })
        }])
      }

      // @see https://sharp.pixelplumbing.com/en/stable/api-input/#sequentialread
      if (args.sequentialRead) {
        queue.unshift([ 'sequentialRead', (sharp) => sharp.sequentialRead() ])
      }

      // @see https://sharp.pixelplumbing.com/en/stable/api-output/#tiff
      if (args.compression !== options.compression.default ||
       args.predictor !== options.predictor.default ||
       args.pyramid || args.quality || args.squash || args.tileHeight ||
       args.tileWidth || args.xres || args.yres) {
        queue.unshift([ 'tiff', (sharp) => {
          return sharp.tiff({
            compression: args.compression,
            force: false,
            predictor: args.predictor,
            pyramid: args.pyramid,
            quality: args.quality,
            squash: args.squash,
            tile: args.tileWidth !== undefined || args.tileHeight !== undefined,
            tileHeight: args.tileHeight || args.tileWidth,
            tileWidth: args.tileWidth || args.tileHeight,
            xres: args.xres,
            yres: args.yres
          })
        }])
      }

      // @see https://sharp.pixelplumbing.com/en/stable/api-output/#webp
      if (args.alphaQuality || args.quality || args.lossless || args.nearLossless) {
        queue.unshift([ 'webp', (sharp) => {
          return sharp.webp({
            alphaQuality: args.alphaQuality,
            force: false,
            lossless: args.lossless,
            nearLossless: args.nearLossless,
            quality: args.quality
          })
        }])
      }

      // @see https://sharp.pixelplumbing.com/en/stable/api-output/#withmetadata
      if (args.withMetadata) {
        queue.unshift([ 'withMetadata', (sharp) => sharp.withMetadata() ])
      }
    }

    // Invoke original.
    return callback(err, args, output)
  })
}

// Exports.
module.exports = cli