/*
Language: CoffeeScript
Author: Dmytrii Nagirniak <dnagir@gmail.com>
Contributors: Oleg Efimov <efimovov@gmail.com>, Cédric Néhémie <cedric.nehemie@gmail.com>
Description: CoffeeScript is a programming language that transcompiles to JavaScript. For info about language see http://coffeescript.org/
Category: common, scripting
*/

import { KeywordsDef, SyntaxDef, LanguageDef } from '../types';
import {
    BINARY_NUMBER_MODE,
    C_NUMBER_MODE,
    BACKSLASH_ESCAPE,
    HASH_COMMENT_MODE,
    TITLE_MODE,
    COMMENT
} from '../common';

const KEYWORDS: KeywordsDef = {
    keyword:
        // JS keywords
        'in if for while finally new do return else break catch instanceof throw try this ' +
        'switch continue typeof delete debugger super yield import export from as default await ' +
        // Coffee keywords
        'then unless until loop of by when and or is isnt not',
    literal:
        // JS literals
        'true false null undefined ' +
        // Coffee literals
        'yes no on off',
    built_in:
        'npm require console print module global window document'
};

const JS_IDENT_RE = '[A-Za-z$_][0-9A-Za-z$_]*';

const SUBST: SyntaxDef = {
    className: 'subst',
    begin: /#\{/, end: /}/,
    keywords: KEYWORDS
};

const EXPRESSIONS = [
    BINARY_NUMBER_MODE,
    { ...C_NUMBER_MODE, starts: { end: '(\\s*/)?', relevance: 0 } }, // a number tries to eat the following slash to prevent treating it as a regexp
    {
        className: 'string',
        variants: [
            {
                begin: /'''/, end: /'''/,
                contains: [BACKSLASH_ESCAPE]
            },
            {
                begin: /'/, end: /'/,
                contains: [BACKSLASH_ESCAPE]
            },
            {
                begin: /"""/, end: /"""/,
                contains: [BACKSLASH_ESCAPE, SUBST]
            },
            {
                begin: /"/, end: /"/,
                contains: [BACKSLASH_ESCAPE, SUBST]
            }
        ]
    },
    {
        className: 'regexp',
        variants: [
            {
                begin: '///', end: '///',
                contains: [SUBST, HASH_COMMENT_MODE]
            },
            {
                begin: '//[gim]*',
                relevance: 0
            },
            {
                // regex can't start with space to parse x / 2 / 3 as two divisions
                // regex can't start with *, and it supports an "illegal" in the main mode
                begin: /\/(?![ *])(\\\/|.)*?\/[gim]*(?=\W|$)/
            }
        ]
    },
    {
        begin: '@' + JS_IDENT_RE // relevance booster
    },
    {
        subLanguage: 'javascript',
        excludeBegin: true, excludeEnd: true,
        variants: [
            {
                begin: '```', end: '```',
            },
            {
                begin: '`', end: '`',
            }
        ]
    }
];

SUBST.contains = EXPRESSIONS;

const TITLE = { ...TITLE_MODE, begin: JS_IDENT_RE };

const PARAMS_RE = '(\\(.*\\))?\\s*\\B[-=]>';

const PARAMS: SyntaxDef = {
    className: 'params',
    begin: '\\([^\\(]', returnBegin: true,
    /* We need another contained nameless mode to not have every nested
    pair of parens to be called "params" */
    contains: [{
        begin: /\(/, end: /\)/,
        keywords: KEYWORDS,
        contains: ['self', ...EXPRESSIONS]
    }]
};

export const CoffeeScript: LanguageDef = {
    name: 'coffeescript',
    aliases: ['coffee', 'cson', 'iced'],
    keywords: KEYWORDS,
    illegal: /\/\*/,
    contains: [
        ...EXPRESSIONS,
        COMMENT('###', '###'),
        HASH_COMMENT_MODE,
        {
            className: 'function',
            begin: '^\\s*' + JS_IDENT_RE + '\\s*=\\s*' + PARAMS_RE, end: '[-=]>',
            returnBegin: true,
            contains: [TITLE, PARAMS]
        },
        {
            // anonymous function start
            begin: /[:\(,=]\s*/,
            relevance: 0,
            contains: [
                {
                    className: 'function',
                    begin: PARAMS_RE, end: '[-=]>',
                    returnBegin: true,
                    contains: [PARAMS]
                }
            ]
        },
        {
            className: 'class',
            beginKeywords: 'class',
            end: '$',
            illegal: /[:="\[\]]/,
            contains: [
                {
                    beginKeywords: 'extends',
                    endsWithParent: true,
                    illegal: /[:="\[\]]/,
                    contains: [TITLE]
                },
                TITLE
            ]
        },
        {
            begin: JS_IDENT_RE + ':', end: ':',
            returnBegin: true, returnEnd: true,
            relevance: 0
        }
    ]
};
