###
  Copyright 2014-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
  Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with
  the License. A copy of the License is located at http://aws.amazon.com/apache2.0/
  or in the "license" file accompanying this file. This file 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.
###

AWS = null
global = null
ignoreRequire = require
if typeof window == 'undefined'
  AWS = require('aws-sdk')
  AMA = require('../lib/ama.js');
  global = GLOBAL
else
  AWS = window.AWS
  AMA = window.AMA
  global = window

if global.jasmine
  global.jasmine.DEFAULT_TIMEOUT_INTERVAL = 30000

_it = it
global.it = (label, fn) ->
  if label.match(/\(no phantomjs\)/) and navigator and navigator.userAgent.match(/phantomjs/i)
    return
  _it(label, fn)

EventEmitter = require('events').EventEmitter
Buffer = AWS.util.Buffer
semver = require('semver')

require('util').print = (data) ->
  process.stdout.write(data)

# Mock credentials
AWS.config.update
  paramValidation: false
  region: 'mock-region'
  credentials:
    accessKeyId: 'akid'
    secretAccessKey: 'secret'
    sessionToken: 'session'

spies = null
beforeEach ->
  spies = []

afterEach ->
  while spies.length > 0
    spy = spies.pop()
    spy.object[spy.methodName] = spy.origMethod

_createSpy = (name) ->
  spy = ->
    spy.calls.push
      object: this
      arguments: Array.prototype.slice.call(arguments)
    if spy.callFn
      return spy.callFn.apply(spy.object, arguments)
    if spy.shouldReturn
      return spy.returnValue
  spy.object = this
  spy.methodName = name
  spy.callFn = null
  spy.shouldReturn = false
  spy.returnValue = null
  spy.calls = []
  spy.andReturn = (value) -> spy.shouldReturn = true; spy.returnValue = value; spy
  spy.andCallFake = (fn) -> spy.callFn = fn; spy
  spy

_spyOn = (obj, methodName) ->
  spy = _createSpy.call(obj, methodName)
  spy.origMethod = obj[methodName]
  spy.andCallThrough = -> spy.callFn = spy.origMethod; spy
  obj[methodName] = spy
  spies.push(spy)
  spy

# Disable setTimeout for tests
# Warning: this might cause unpredictable results
# TODO: refactor this out.
#global.setTimeout = (fn) -> console.log(fn.name + ' called')

global.expect = require('chai').expect

matchXML = (xml1, xml2) ->
  results = []
  parser = new (require('xml2js').Parser)()
  [xml1, xml2].forEach (xml) ->
    parser.parseString xml, (e,r) ->
      if e then throw e
      results.push(r)
  expect(results[0]).to.eql(results[1])

MockService = AWS.Service.defineService 'mockService',
  serviceIdentifier: 'mock'
  initialize: (config) ->
    AWS.Service.prototype.initialize.call(this, config)
    @config.credentials = accessKeyId: 'akid', secretAccessKey: 'secret'
    @config.region = 'mock-region'
  setupRequestListeners: (request) ->
    request.on 'extractData', (resp) ->
      resp.data = (resp.httpResponse.body||'').toString()
    request.on 'extractError', (resp) ->
      resp.error =
        code: (resp.httpResponse.body||'').toString() || resp.httpResponse.statusCode
        message: null
  api: new AWS.Model.Api metadata:
    endpointPrefix: 'mockservice'
    signatureVersion: 'v4'

mockHttpSuccessfulResponse = (status, headers, data, cb) ->
  if !Array.isArray(data)
    data = [data]

  httpResp = new EventEmitter()
  httpResp.statusCode = status
  httpResp.headers = headers

  cb(httpResp)
  httpResp.emit('headers', status, headers)

  if AWS.util.isNode() && httpResp._events.readable
    httpResp.read = ->
      if data.length > 0
        chunk = data.shift()
        if chunk is null
          null
        else
          new Buffer(chunk)
      else
        null

  AWS.util.arrayEach data.slice(), (str) ->
    if AWS.util.isNode() && (httpResp._events.readable || semver.gt(process.version, 'v0.11.3'))
      httpResp.emit('readable')
    else
      httpResp.emit('data', new Buffer(str))

  if httpResp._events['readable'] || httpResp._events['data']
    httpResp.emit('end')
  else
    httpResp.emit('aborted')

mockHttpResponse = (status, headers, data) ->
  stream = new EventEmitter()
  stream.setMaxListeners(0)
  _spyOn(AWS.HttpClient, 'getInstance')
  AWS.HttpClient.getInstance.andReturn handleRequest: (req, opts, cb, errCb) ->
    if typeof status == 'number'
      mockHttpSuccessfulResponse status, headers, data, cb
    else
      errCb(status)
    stream

  return stream

mockIntermittentFailureResponse = (numFailures, status, headers, data) ->
  retryCount = 0
  _spyOn(AWS.HttpClient, 'getInstance')
  AWS.HttpClient.getInstance.andReturn handleRequest: (req, opts, cb, errCb) ->
    if retryCount < numFailures
      retryCount += 1
      errCb code: 'NetworkingError', message: 'FAIL!'
    else
      statusCode = retryCount < numFailures ? 500 : status
      mockHttpSuccessfulResponse statusCode, headers, data, cb
    new EventEmitter()

globalEvents = null
beforeEach -> globalEvents = AWS.events
afterEach -> AWS.events = globalEvents

setupMockResponse = (cb) ->
  AWS.events = new AWS.SequentialExecutor()
  AWS.events.on 'validate', (req) ->
    ['sign', 'send'].forEach (evt) -> req.removeAllListeners(evt)
    req.removeListener('extractData', AWS.EventListeners.CorePost.EXTRACT_REQUEST_ID)
    Object.keys(AWS.EventListeners).forEach (ns) ->
      if AWS.EventListeners[ns].EXTRACT_DATA
        req.removeListener('extractData', AWS.EventListeners[ns].EXTRACT_DATA)
      if AWS.EventListeners[ns].EXTRACT_ERROR
        req.removeListener('extractError', AWS.EventListeners[ns].EXTRACT_ERROR)
    req.response.httpResponse.statusCode = 200
    req.removeListener('validateResponse', AWS.EventListeners.Core.VALIDATE_RESPONSE)
    req.on('validateResponse', cb)

mockResponse = (resp) ->
  reqs = []
  setupMockResponse (response) ->
    reqs.push(response.request)
    AWS.util.update response, resp
  reqs

mockResponses = (resps) ->
  index = 0
  reqs = []
  setupMockResponse (response) ->
    reqs.push(response.request)
    resp = resps[index]
    AWS.util.update response, resp
    index += 1

  reqs

operationsForRequests = (reqs) ->
  reqs.map (req) ->
    req.service.serviceIdentifier + '.' + req.operation

module.exports =
  AWS: AWS
  AMA: AMA
  util: AWS.util
  spyOn: _spyOn
  createSpy: _createSpy
  matchXML: matchXML
  mockHttpResponse: mockHttpResponse
  mockIntermittentFailureResponse: mockIntermittentFailureResponse
  mockHttpSuccessfulResponse: mockHttpSuccessfulResponse
  mockResponse: mockResponse
  mockResponses: mockResponses
  operationsForRequests: operationsForRequests
  MockService: MockService

