'use strict';

import { InternalServer } from './server-container';
import { HttpMethod } from './server-types';
import * as metadata from './metadata';
import 'reflect-metadata';
import * as _ from 'lodash';

/**
 * A decorator to tell the [[Server]] that a class or a method
 * should be bound to a given path.
 *
 * For example:
 *
 * ```
 * @ Path('people')
 * class PeopleService {
 *   @ PUT
 *   @ Path(':id')
 *   savePerson(person:Person) {
 *      // ...
 *   }
 *
 *   @ GET
 *   @ Path(':id')
 *   getPerson():Person {
 *      // ...
 *   }
 * }
 * ```
 *
 * Will create services that listen for requests like:
 *
 * ```
 * PUT http://mydomain/people/123 or
 * GET http://mydomain/people/123
 * ```
 */
export function Path(path: string) {
    return function(...args: any[]) {
        args = _.without(args, undefined);
        if (args.length === 1) {
            return PathTypeDecorator.apply(this, [args[0], path]);
        } else if (args.length === 3 && typeof args[2] === 'object') {
            return PathMethodDecorator.apply(this, [args[0], args[1], args[2], path]);
        }

        throw new Error('Invalid @Path Decorator declaration.');
    };
}

/**
 * A decorator to tell the [[Server]] that a class or a method
 * should only accept requests from clients that accepts one of
 * the supported languages.
 *
 * For example:
 *
 * ```
 * @ Path('accept')
 * @ AcceptLanguage('en', 'pt-BR')
 * class TestAcceptService {
 *      // ...
 * }
 * ```
 *
 * Will reject requests that only accepts languages that are not
 * English or Brazilian portuguese
 *
 * If the language requested is not supported, a status code 406 returned
 */
export function AcceptLanguage(...languages: string[]) {
    return function(...args: any[]) {
        args = _.without(args, undefined);
        if (args.length === 1) {
            return AcceptLanguageTypeDecorator.apply(this, [args[0], languages]);
        } else if (args.length === 3 && typeof args[2] === 'object') {
            return AcceptLanguageMethodDecorator.apply(this, [args[0], args[1], args[2], languages]);
        }

        throw new Error('Invalid @AcceptLanguage Decorator declaration.');
    };
}

/**
 * A decorator to tell the [[Server]] that a class or a method
 * should only accept requests from clients that accepts one of
 * the supported mime types.
 *
 * For example:
 *
 * ```
 * @ Path('accept')
 * @ Accept('application/json')
 * class TestAcceptService {
 *      // ...
 * }
 * ```
 *
 * Will reject requests that only accepts mime types that are not
 * 'application/json'
 *
 * If the mime type requested is not supported, a status code 406 returned
 */
export function Accept(...accepts: string[]) {
    return function(...args: any[]) {
        args = _.without(args, undefined);
        if (args.length === 1) {
            return AcceptTypeDecorator.apply(this, [args[0], accepts]);
        } else if (args.length === 3 && typeof args[2] === 'object') {
            return AcceptMethodDecorator.apply(this, [args[0], args[1], args[2], accepts]);
        }

        throw new Error('Invalid @Accept Decorator declaration.');
    };
}

/**
 * A decorator to be used on class properties or on service method arguments
 * to inform that the decorated property or argument should be bound to the
 * [[ServiceContext]] object associated to the current request.
 *
 * For example:
 *
 * ```
 * @ Path('context')
 * class TestService {
 *   @ Context
	 context: ServiceContext;
 *       // ...
 * }
 * ```
 *
 * The field context on the above class will point to the current
 * [[ServiceContext]] instance.
 */
export function Context(...args: any[]) {
    args = _.without(args, undefined);
    const newArgs = args.concat([metadata.ParamType.context, null]);
    if (args.length < 3 || typeof args[2] === 'undefined') {
        return processDecoratedProperty.apply(this, newArgs);
    } else if (args.length === 3 && typeof args[2] === 'number') {
        return processDecoratedParameter.apply(this, newArgs);
    }

    throw new Error('Invalid @Context Decorator declaration.');
}

/**
 * A decorator to be used on class properties or on service method arguments
 * to inform that the decorated property or argument should be bound to the
 * the current request.
 *
 * For example:
 *
 * ```
 * @ Path('context')
 * class TestService {
 *   @ ContextRequest
	 request: express.Request;
 *       // ...
 * }
 * ```
 *
 * The field request on the above class will point to the current
 * request.
 */
export function ContextRequest(...args: any[]) {
    args = _.without(args, undefined);
    const newArgs = args.concat([metadata.ParamType.context_request, null]);
    if (args.length < 3 || typeof args[2] === 'undefined') {
        return processDecoratedProperty.apply(this, newArgs);
    } else if (args.length === 3 && typeof args[2] === 'number') {
        return processDecoratedParameter.apply(this, newArgs);
    }

    throw new Error('Invalid @ContextRequest Decorator declaration.');
}

/**
 * A decorator to be used on class properties or on service method arguments
 * to inform that the decorated property or argument should be bound to the
 * the current response object.
 *
 * For example:
 *
 * ```
 * @ Path('context')
 * class TestService {
 *   @ ContextResponse
	 response: express.Response;
 *       // ...
 * }
 * ```
 *
 * The field response on the above class will point to the current
 * response object.
 */
export function ContextResponse(...args: any[]) {
    args = _.without(args, undefined);
    const newArgs = args.concat([metadata.ParamType.context_response, null]);
    if (args.length < 3 || typeof args[2] === 'undefined') {
        return processDecoratedProperty.apply(this, newArgs);
    } else if (args.length === 3 && typeof args[2] === 'number') {
        return processDecoratedParameter.apply(this, newArgs);
    }

    throw new Error('Invalid @ContextResponse Decorator declaration.');
}

/**
 * A decorator to be used on class properties or on service method arguments
 * to inform that the decorated property or argument should be bound to the
 * the next function.
 *
 * For example:
 *
 * ```
 * @ Path('context')
 * class TestService {
 *   @ ContextNext
 *   next: express.NextFunction
 *       // ...
 * }
 * ```
 *
 * The next function can be used to delegate to the next registered
 * middleware the current request processing.
 */
export function ContextNext(...args: any[]) {
    args = _.without(args, undefined);
    const newArgs = args.concat([metadata.ParamType.context_next, null]);
    if (args.length < 3 || typeof args[2] === 'undefined') {
        return processDecoratedProperty.apply(this, newArgs);
    } else if (args.length === 3 && typeof args[2] === 'number') {
        return processDecoratedParameter.apply(this, newArgs);
    }

    throw new Error('Invalid @ContextNext Decorator declaration.');
}

/**
 * A decorator to be used on class properties or on service method arguments
 * to inform that the decorated property or argument should be bound to the
 * the current context language.
 *
 * For example:
 *
 * ```
 * @ Path('context')
 * class TestService {
 *   @ ContextLanguage
 *   language: string
 *       // ...
 * }
 * ```
 */
export function ContextLanguage(...args: any[]) {
    args = _.without(args, undefined);
    const newArgs = args.concat([metadata.ParamType.context_accept_language, null]);
    if (args.length < 3 || typeof args[2] === 'undefined') {
        return processDecoratedProperty.apply(this, newArgs);
    } else if (args.length === 3 && typeof args[2] === 'number') {
        return processDecoratedParameter.apply(this, newArgs);
    }

    throw new Error('Invalid @ContextLanguage Decorator declaration.');
}

/**
 * A decorator to be used on class properties or on service method arguments
 * to inform that the decorated property or argument should be bound to the
 * the preferred media type for the current request.
 *
 * For example:
 *
 * ```
 * @ Path('context')
 * class TestService {
 *   @ ContextAccept
 *   media: string
 *       // ...
 * }
 * ```
 */
export function ContextAccept(...args: any[]) {
    args = _.without(args, undefined);
    const newArgs = args.concat([metadata.ParamType.context_accept, null]);
    if (args.length < 3 || typeof args[2] === 'undefined') {
        return processDecoratedProperty.apply(this, newArgs);
    } else if (args.length === 3 && typeof args[2] === 'number') {
        return processDecoratedParameter.apply(this, newArgs);
    }

    throw new Error('Invalid @ContextAccept Decorator declaration.');
}

/**
 * A decorator to tell the [[Server]] that a method
 * should be called to process HTTP GET requests.
 *
 * For example:
 *
 * ```
 * @ Path('people')
 * class PeopleService {
 *   @ GET
 *   getPeople() {
 *      // ...
 *   }
 * }
 * ```
 *
 * Will create a service that listen for requests like:
 *
 * ```
 * GET http://mydomain/people
 * ```
 */
export function GET(target: any, propertyKey: string,
    descriptor: PropertyDescriptor) {
    processHttpVerb(target, propertyKey, HttpMethod.GET);
}

/**
 * A decorator to tell the [[Server]] that a method
 * should be called to process HTTP POST requests.
 *
 * For example:
 *
 * ```
 * @ Path('people')
 * class PeopleService {
 *   @ POST
 *   addPerson() {
 *      // ...
 *   }
 * }
 * ```
 *
 * Will create a service that listen for requests like:
 *
 * ```
 * POST http://mydomain/people
 * ```
 */
export function POST(target: any, propertyKey: string,
    descriptor: PropertyDescriptor) {
    processHttpVerb(target, propertyKey, HttpMethod.POST);
}

/**
 * A decorator to tell the [[Server]] that a method
 * should be called to process HTTP PUT requests.
 *
 * For example:
 *
 * ```
 * @ Path('people')
 * class PeopleService {
 *   @ PUT
 *   @ Path(':id')
 *   savePerson(person: Person) {
 *      // ...
 *   }
 * }
 * ```
 *
 * Will create a service that listen for requests like:
 *
 * ```
 * PUT http://mydomain/people/123
 * ```
 */
export function PUT(target: any, propertyKey: string,
    descriptor: PropertyDescriptor) {
    processHttpVerb(target, propertyKey, HttpMethod.PUT);
}

/**
 * A decorator to tell the [[Server]] that a method
 * should be called to process HTTP DELETE requests.
 *
 * For example:
 *
 * ```
 * @ Path('people')
 * class PeopleService {
 *   @ DELETE
 *   @ Path(':id')
 *   removePerson(@ PathParam('id')id: string) {
 *      // ...
 *   }
 * }
 * ```
 *
 * Will create a service that listen for requests like:
 *
 * ```
 * PUT http://mydomain/people/123
 * ```
 */
export function DELETE(target: any, propertyKey: string,
    descriptor: PropertyDescriptor) {
    processHttpVerb(target, propertyKey, HttpMethod.DELETE);
}

/**
 * A decorator to tell the [[Server]] that a method
 * should be called to process HTTP HEAD requests.
 *
 * For example:
 *
 * ```
 * @ Path('people')
 * class PeopleService {
 *   @ HEAD
 *   headPerson() {
 *      // ...
 *   }
 * }
 * ```
 *
 * Will create a service that listen for requests like:
 *
 * ```
 * HEAD http://mydomain/people/123
 * ```
 */
export function HEAD(target: any, propertyKey: string,
    descriptor: PropertyDescriptor) {
    processHttpVerb(target, propertyKey, HttpMethod.HEAD);
}

/**
 * A decorator to tell the [[Server]] that a method
 * should be called to process HTTP OPTIONS requests.
 *
 * For example:
 *
 * ```
 * @ Path('people')
 * class PeopleService {
 *   @ OPTIONS
 *   optionsPerson() {
 *      // ...
 *   }
 * }
 * ```
 *
 * Will create a service that listen for requests like:
 *
 * ```
 * OPTIONS http://mydomain/people/123
 * ```
 */
export function OPTIONS(target: any, propertyKey: string,
    descriptor: PropertyDescriptor) {
    processHttpVerb(target, propertyKey, HttpMethod.OPTIONS);
}

/**
 * A decorator to tell the [[Server]] that a method
 * should be called to process HTTP PATCH requests.
 *
 * For example:
 *
 * ```
 * @ Path('people')
 * class PeopleService {
 *   @ PATCH
 *   @ Path(':id')
 *   savePerson(person: Person) {
 *      // ...
 *   }
 * }
 * ```
 *
 * Will create a service that listen for requests like:
 *
 * ```
 * PATCH http://mydomain/people/123
 * ```
 */
export function PATCH(target: any, propertyKey: string,
    descriptor: PropertyDescriptor) {
    processHttpVerb(target, propertyKey, HttpMethod.PATCH);
}

/**
 * A decorator to inform options to pe passed to bodyParser.
 * You can inform any property accepted by
 * [[bodyParser]](https://www.npmjs.com/package/body-parser)
 */
export function BodyOptions(options: any) {
    return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        const serviceMethod: metadata.ServiceMethod = InternalServer.registerServiceMethod(target.constructor, propertyKey);
        if (serviceMethod) { // does not intercept constructor
            serviceMethod.bodyParserOptions = options;
        }
    };
}

/**
 * Creates a mapping between a fragment of the requested path and
 * a method argument.
 *
 * For example:
 *
 * ```
 * @ Path('people')
 * class PeopleService {
 *   @ GET
 *   @ Path(':id')
 *   getPerson(@ PathParam('id') id: string) {
 *      // ...
 *   }
 * }
 * ```
 *
 * Will create a service that listen for requests like:
 *
 * ```
 * GET http://mydomain/people/123
 * ```
 *
 * And pass 123 as the id argument on getPerson method's call.
 */
export function PathParam(name: string) {
    return function(...args: any[]) {
        args = _.without(args, undefined);
        const newArgs = args.concat([metadata.ParamType.path, name]);
        if (args.length < 3 || typeof args[2] === 'undefined') {
            return processDecoratedProperty.apply(this, newArgs);
        } else if (args.length === 3 && typeof args[2] === 'number') {
            return processDecoratedParameter.apply(this, newArgs);
        }

        throw new Error('Invalid @PathParam Decorator declaration.');
    };
}

/**
 * Creates a mapping between a file on a multipart request and a method
 * argument.
 *
 * For example:
 *
 * ```
 * @ Path('people')
 * class PeopleService {
 *   @ POST
 *   @ Path('id')
 *   addAvatar(@ PathParam('id') id: string,
 *             @ FileParam('avatar') file: Express.Multer.File) {
 *      // ...
 *   }
 * }
 * ```
 *
 * Will create a service that listen for requests and bind the
 * file with name 'avatar' on the requested form to the file
 * argument on addAvatar method's call.
 */
export function FileParam(name: string) {
    return function(...args: any[]) {
        args = _.without(args, undefined);
        const newArgs = args.concat([metadata.ParamType.file, name]);
        if (args.length < 3 || typeof args[2] === 'undefined') {
            return processDecoratedProperty.apply(this, newArgs);
        } else if (args.length === 3 && typeof args[2] === 'number') {
            return processDecoratedParameter.apply(this, newArgs);
        }

        throw new Error('Invalid @FileParam Decorator declaration.');
    };
}

/**
 * Creates a mapping between a list of files on a multipart request and a method
 * argument.
 *
 * For example:
 *
 * ```
 * @ Path('people')
 * class PeopleService {
 *   @ POST
 *   @ Path('id')
 *   addAvatar(@ PathParam('id') id: string,
 *             @ FilesParam('avatar') Array<file>: Express.Multer.File) {
 *      // ...
 *   }
 * }
 * ```
 *
 * Will create a service that listen for requests and bind the
 * files with name 'avatar' on the request form to the file
 * argument on addAvatar method's call.
 */
export function FilesParam(name: string) {
    return function(...args: any[]) {
        args = _.without(args, undefined);
        const newArgs = args.concat([metadata.ParamType.files, name]);
        if (args.length < 3 || typeof args[2] === 'undefined') {
            return processDecoratedProperty.apply(this, newArgs);
        } else if (args.length === 3 && typeof args[2] === 'number') {
            return processDecoratedParameter.apply(this, newArgs);
        }

        throw new Error('Invalid @FilesParam Decorator declaration.');
    };
}

/**
 * Creates a mapping between a query parameter on request and a method
 * argument.
 *
 * For example:
 *
 * ```
 * @ Path('people')
 * class PeopleService {
 *   @ GET
 *   getPeople(@ QueryParam('name') name: string) {
 *      // ...
 *   }
 * }
 * ```
 *
 * Will create a service that listen for requests like:
 *
 * ```
 * GET http://mydomain/people?name=joe
 * ```
 *
 * And pass 'joe' as the name argument on getPerson method's call.
 */
export function QueryParam(name: string) {
    return function(...args: any[]) {
        args = _.without(args, undefined);
        const newArgs = args.concat([metadata.ParamType.query, name]);
        if (args.length < 3 || typeof args[2] === 'undefined') {
            return processDecoratedProperty.apply(this, newArgs);
        } else if (args.length === 3 && typeof args[2] === 'number') {
            return processDecoratedParameter.apply(this, newArgs);
        }

        throw new Error('Invalid @QueryParam Decorator declaration.');
    };
}

/**
 * Creates a mapping between a header on request and a method
 * argument.
 *
 * For example:
 *
 * ```
 * @ Path('people')
 * class PeopleService {
 *   @ GET
 *   getPeople(@ HeaderParam('header') header: string) {
 *      // ...
 *   }
 * }
 * ```
 *
 * Will create a service that listen for requests and bind the
 * header called 'header' to the header argument on getPerson method's call.
 */
export function HeaderParam(name: string) {
    return function(...args: any[]) {
        args = _.without(args, undefined);
        const newArgs = args.concat([metadata.ParamType.header, name]);
        if (args.length < 3 || typeof args[2] === 'undefined') {
            return processDecoratedProperty.apply(this, newArgs);
        } else if (args.length === 3 && typeof args[2] === 'number') {
            return processDecoratedParameter.apply(this, newArgs);
        }

        throw new Error('Invalid @HeaderParam Decorator declaration.');
    };
}

/**
 * Creates a mapping between a cookie on request and a method
 * argument.
 *
 * For example:
 *
 * ```
 * @ Path('people')
 * class PeopleService {
 *   @ GET
 *   getPeople(@ CookieParam('cookie') cookie: string) {
 *      // ...
 *   }
 * }
 * ```
 *
 * Will create a service that listen for requests and bind the
 * cookie called 'cookie' to the cookie argument on getPerson method's call.
 */
export function CookieParam(name: string) {
    return function(...args: any[]) {
        args = _.without(args, undefined);
        const newArgs = args.concat([metadata.ParamType.cookie, name]);
        if (args.length < 3 || typeof args[2] === 'undefined') {
            return processDecoratedProperty.apply(this, newArgs);
        } else if (args.length === 3 && typeof args[2] === 'number') {
            return processDecoratedParameter.apply(this, newArgs);
        }

        throw new Error('Invalid @CookieParam Decorator declaration.');
    };
}

/**
 * Creates a mapping between a form parameter on request and a method
 * argument.
 *
 * For example:
 *
 * ```
 * @ Path('people')
 * class PeopleService {
 *   @ GET
 *   getPeople(@ FormParam('name') name: string) {
 *      // ...
 *   }
 * }
 * ```
 *
 * Will create a service that listen for requests and bind the
 * request paramenter called 'name' to the name argument on getPerson
 * method's call.
 */
export function FormParam(name: string) {
    return function(...args: any[]) {
        args = _.without(args, undefined);
        const newArgs = args.concat([metadata.ParamType.form, name]);
        if (args.length < 3 || typeof args[2] === 'undefined') {
            return processDecoratedProperty.apply(this, newArgs);
        } else if (args.length === 3 && typeof args[2] === 'number') {
            return processDecoratedParameter.apply(this, newArgs);
        }

        throw new Error('Invalid @FormParam Decorator declaration.');
    };
}

/**
 * Creates a mapping between a parameter on request and a method
 * argument.
 *
 * For example:
 *
 * ```
 * @ Path('people')
 * class PeopleService {
 *   @ GET
 *   getPeople(@ Param('name') name: string) {
 *      // ...
 *   }
 * }
 * ```
 *
 * Will create a service that listen for requests and bind the
 * request paramenter called 'name' to the name argument on getPerson
 * method's call. It will work to query parameters or form parameters
 * received in the current request.
 */
export function Param(name: string) {
    return function(...args: any[]) {
        args = _.without(args, undefined);
        const newArgs = args.concat([metadata.ParamType.param, name]);
        if (args.length < 3 || typeof args[2] === 'undefined') {
            return processDecoratedProperty.apply(this, newArgs);
        } else if (args.length === 3 && typeof args[2] === 'number') {
            return processDecoratedParameter.apply(this, newArgs);
        }

        throw new Error('Invalid @Param Decorator declaration.');
    };
}

/**
 * Decorator processor for [[AcceptLanguage]] decorator on classes
 */
function AcceptLanguageTypeDecorator(target: Function, languages: string[]) {
    const classData: metadata.ServiceClass = InternalServer.registerServiceClass(target);
    classData.languages = _.union(classData.languages, languages);
}

/**
 * Decorator processor for [[AcceptLanguage]] decorator on methods
 */
function AcceptLanguageMethodDecorator(target: any, propertyKey: string,
    descriptor: PropertyDescriptor, languages: string[]) {
    const serviceMethod: metadata.ServiceMethod = InternalServer.registerServiceMethod(target.constructor, propertyKey);
    if (serviceMethod) { // does not intercept constructor
        serviceMethod.languages = languages;
    }
}

/**
 * Decorator processor for [[Accept]] decorator on classes
 */
function AcceptTypeDecorator(target: Function, accepts: string[]) {
    const classData: metadata.ServiceClass = InternalServer.registerServiceClass(target);
    classData.accepts = _.union(classData.accepts, accepts);
}

/**
 * Decorator processor for [[Accept]] decorator on methods
 */
function AcceptMethodDecorator(target: any, propertyKey: string,
    descriptor: PropertyDescriptor, accepts: string[]) {
    const serviceMethod: metadata.ServiceMethod = InternalServer.registerServiceMethod(target.constructor, propertyKey);
    if (serviceMethod) { // does not intercept constructor
        serviceMethod.accepts = accepts;
    }
}

/**
 * Decorator processor for [[Path]] decorator on classes
 */
function PathTypeDecorator(target: Function, path: string) {
    const classData: metadata.ServiceClass = InternalServer.registerServiceClass(target);
    classData.path = path;
}

/**
 * Decorator processor for [[Path]] decorator on methods
 */
function PathMethodDecorator(target: any, propertyKey: string,
    descriptor: PropertyDescriptor, path: string) {
    const serviceMethod: metadata.ServiceMethod = InternalServer.registerServiceMethod(target.constructor, propertyKey);
    if (serviceMethod) { // does not intercept constructor
        serviceMethod.path = path;
    }
}

/**
 * Decorator processor for parameter annotations on methods
 */
function processDecoratedParameter(target: Object, propertyKey: string, parameterIndex: number,
    paramType: metadata.ParamType, name: string) {
    const serviceMethod: metadata.ServiceMethod = InternalServer.registerServiceMethod(target.constructor, propertyKey);
    if (serviceMethod) { // does not intercept constructor
        const paramTypes = Reflect.getOwnMetadata('design:paramtypes', target, propertyKey);

        while (paramTypes && serviceMethod.parameters.length < paramTypes.length) {
            serviceMethod.parameters.push(new metadata.MethodParam(null,
                paramTypes[serviceMethod.parameters.length], metadata.ParamType.body));
        }
        serviceMethod.parameters[parameterIndex] = new metadata.MethodParam(name, paramTypes[parameterIndex], paramType);
    }
}

/**
 * Decorator processor for annotations on properties
 */
function processDecoratedProperty(target: Function, key: string, paramType: metadata.ParamType, paramName: string) {
    const classData: metadata.ServiceClass = InternalServer.registerServiceClass(target.constructor);
    const propertyType = Reflect.getMetadata('design:type', target, key);
    classData.addProperty(key, paramType, paramName, propertyType);
}

/**
 * Decorator processor for HTTP verb annotations on methods
 */
function processHttpVerb(target: any, propertyKey: string,
    httpMethod: HttpMethod) {
    const serviceMethod: metadata.ServiceMethod = InternalServer.registerServiceMethod(target.constructor, propertyKey);
    if (serviceMethod) { // does not intercept constructor
        if (serviceMethod.httpMethod) {
            throw new Error('Method is already annotated with @' +
                serviceMethod.httpMethod +
                '. You can only map a method to one HTTP verb.');
        }
        serviceMethod.httpMethod = httpMethod;
        processServiceMethod(target, propertyKey, serviceMethod);
    }
}

/**
 * Extract metadata for rest methods
 */
function processServiceMethod(target: any, propertyKey: string, serviceMethod: metadata.ServiceMethod) {
    serviceMethod.name = propertyKey;
    const paramTypes = Reflect.getOwnMetadata('design:paramtypes', target, propertyKey);
    while (paramTypes && paramTypes.length > serviceMethod.parameters.length) {
        serviceMethod.parameters.push(new metadata.MethodParam(null,
            paramTypes[serviceMethod.parameters.length], metadata.ParamType.body));
    }

    serviceMethod.parameters.forEach(param => {
        if (param.paramType === metadata.ParamType.cookie) {
            serviceMethod.mustParseCookies = true;
        } else if (param.paramType === metadata.ParamType.file) {
            serviceMethod.files.push(new metadata.FileParam(param.name, true));
        } else if (param.paramType === metadata.ParamType.files) {
            serviceMethod.files.push(new metadata.FileParam(param.name, false));
        } else if (param.paramType === metadata.ParamType.param) {
            serviceMethod.acceptMultiTypedParam = true;
        } else if (param.paramType === metadata.ParamType.form) {
            if (serviceMethod.mustParseBody) {
                throw Error('Can not use form parameters with a body parameter on the same method.');
            }
            serviceMethod.mustParseForms = true;
        } else if (param.paramType === metadata.ParamType.body) {
            if (serviceMethod.mustParseForms) {
                throw Error('Can not use form parameters with a body parameter on the same method.');
            }
            if (serviceMethod.mustParseBody) {
                throw Error('Can not use more than one body parameter on the same method.');
            }
            serviceMethod.mustParseBody = true;
        }
    });
}
