#!/usr/bin/env coffee

fs = require "fs"
request = require "request"
path = require "path"
url = require "url"
_ = require "underscore"
_.str = require "underscore.string"
handlebars = require "handlebars"
argv = require("yargs").argv
mkdirp = require "mkdirp"

if argv._.length == 0
  throw "Expected: swagged-angular-resources swagger-docs-url|swagger-docs-file [--ngmodule swaggedAngularResources [--strip-trailing-slashes false [--output index.js [--mock-output false]]]]"

providerTemplate = "#{__dirname}/../templates/resource-providers.hbs"
mockTemplate = "#{__dirname}/../templates/httpBackend-mocks.hbs"

fileOrUrl = argv._[0]
ngModule = argv.ngModule || "swaggedAngularResources"
ngModuleOutput = argv.output || "index.js"
ngdoc = !!argv.ngdoc
ngMockModuleOutput = argv.mock

log = () -> console.log.apply this, arguments

registerHelpers = (fns) ->
  candidateHelpers = _.filter(_.keys(fns), (fnName) -> _.isFunction fns[fnName])
  _.each(candidateHelpers, (fnName) ->
    handlebars.registerHelper(fnName, () ->
      args = Array.prototype.slice.call(arguments, 0).slice(0, -1);
      fns[fnName].apply(this, args);
    )
  )
registerHelpers(_.str)

readUrlAsJSON = (url, cb) -> request.get({url: url.href, json: true}, (error, res, result) -> cb(error, result))
readFileAsJSON = (file, cb) -> fs.readFile(file, {encoding: "utf-8"}, (error, file) -> cb(error, JSON.parse(file)))

getParameters = (type, parameters) ->
  params = _.filter(parameters, (parameter) -> parameter.in == type)
  if params.length == 0 then false else params

getResourceOperations = (apiDefinition) ->
  if apiDefinition.tags
    tags = _.map apiDefinition.tags, (tag) -> tag.name
  else
    tags = _.reduce apiDefinition.paths, (memo, methods) ->
      _.each methods, (operation) ->
        memo = memo.concat(operation.tags)
      memo
    , []
    tags = _.uniq tags

  _.reduce apiDefinition.paths, (memo, methods, path) ->
    _.each methods, (operation, action) ->
      inTags = _.intersection operation.tags, tags

      _.each inTags, (tag) ->
        response = operation.responses["200"]
        if response and response.schema
          if response.schema.type == "array"
            modelDefinition = response.schema.items.$ref;
            isQuery = true
          else
            modelDefinition = response.schema.$ref

        # TODO: add parameter for mimeType
        mockedResponseMimeType = "application/json"
        mockedResponse = JSON.stringify(response.examples[mockedResponseMimeType], null, 4) if response and response.examples and response.examples[mockedResponseMimeType]

        modelDefinition = modelDefinition and modelDefinition.match(/.+\/(.+)$/)[1]
        memo[tag] = memo[tag] or []
        memo[tag].push {
          path: path.replace(/\{(.+?)\}/g, ":$1")
          nickname: operation.operationId || modelDefinition + "_" + _.str.capitalize(action)
          action: action.toUpperCase()
          summary: operation.summary

          # mock example data
          mockedResponse: mockedResponse

          # build path parameters
          pathParameters: getParameters("path", operation.parameters)

          # get actions
          isQuery: isQuery and action == "get"
          isGet: !isQuery and action == "get"

          # non-get actions
          isDelete: action == "delete"
          isPost: action == "post"
          isPut: action == "put"
          isPatch: action == "patch"
        }
    memo
  , {}

getCode = (error, apiDefinition) ->
  throw error if error

  resourceOperations = getResourceOperations(apiDefinition)

  context = {
    ngModule: ngModule
    angularProviderType: "provider"
    angularProviderSuffix: ""
    resourceOperations: resourceOperations
    ngdoc: ngdoc
  }

  # TODO encoding options and write readWrite function
  fs.readFile(providerTemplate, {encoding: "utf-8"}, (error, template) ->
    if error
      throw error
    else
      code = handlebars.compile(template)(context)
      mkdirp.sync path.dirname(ngModuleOutput) if not fs.existsSync ngModuleOutput
        
      fs.writeFile(ngModuleOutput, code, {encoding: "utf-8"}, (error) ->
        if error
          throw error
      )
  )

  if ngMockModuleOutput
    fs.readFile(mockTemplate, {encoding: "utf-8"}, (error, template) ->
      if error
        throw error
      else
        code = handlebars.compile(template)(context)
        mkdirp.sync path.dirname(ngMockModuleOutput) if not fs.existsSync ngMockModuleOutput
        fs.writeFile(ngMockModuleOutput, code, {encoding: "utf-8"}, (error) ->
          if error
            throw error
        )
    )

apiUrlOrFile = url.parse fileOrUrl

if apiUrlOrFile.protocol != null
  readUrlAsJSON apiUrlOrFile, getCode
else
  readFileAsJSON apiUrlOrFile.path, getCode
