# EXPRESSER MAILER
# --------------------------------------------------------------------------
# Sends and manages emails, supports templates. When parsing templates, the
# tags should be wrapped with normal brackets {}. Example: {contents}
# The base message template (which is loaded with every single sent message)
# must be saved as base.html, under the /emailtemplates folder (or whatever
# folder / base file name you have set on the settings).
# <!--
# @see Settings.mailer
# -->
class Mailer

    events = require "./events.coffee"
    fs = require "fs"
    lodash = require "lodash"
    logger = require "./logger.coffee"
    mailer = require "nodemailer"
    moment = require "moment"
    path = require "path"
    settings = require "./settings.coffee"
    utils = require "./utils.coffee"

    # SMTP objects will be instantiated on `init`.
    smtp = null
    smtp2 = null

    # Templates cache to avoid disk reads.
    templateCache = {}

    # CONSTRUCTOR AND INIT
    # --------------------------------------------------------------------------

    # Mailer constructor.
    constructor: ->
        @setEvents() if settings.events.enabled

    # Bind event listeners.
    setEvents: =>
        events.on "mailer.send", @send

    # Init the Mailer module and create the SMTP objects.
    # @param [Object] options Mailer init options.
    init: (options) =>
        if settings.mailer.smtp.service? and settings.mailer.smtp.service isnt ""
            @setSmtp settings.mailer.smtp, false
        else if settings.mailer.smtp.host? and settings.mailer.smtp.host isnt "" and settings.mailer.smtp.port > 0
            @setSmtp settings.mailer.smtp, false

        if settings.mailer.smtp2.service? and settings.mailer.smtp2.service isnt ""
            @setSmtp settings.mailer.smtp2, true
        else if settings.mailer.smtp2.host? and settings.mailer.smtp2.host isnt "" and settings.mailer.smtp2.port > 0
            @setSmtp settings.mailer.smtp2, true

        # Alert user if specified backup SMTP but not the main one.
        if not smtp? and smtp2?
            logger.warn "Mailer.init", "The secondary SMTP settings are defined but not the main one.", "Will still work, but you should set the main one instead."

        # Warn if no SMTP is available for sending emails, but only when debug is enabled.
        if not smtp? and not smtp2? and settings.general.debug
            logger.warn "Mailer.init", "No main SMTP host/port specified.", "No emails will be sent out."

    # Check if configuration for sending emails is properly set.
    # @return [Boolean] Returns true if smtp server is active, otherwise false.
    checkConfig: =>
        if smtp or smtp2?
            return true
        else
            return false

    # OUTBOUND
    # --------------------------------------------------------------------------

    # Sends an email to the specified address. A callback can be specified, having (err, result).
    # @param [String] options The email message options
    # @option options [String] body The email body in text or HTML.
    # @option options [String] subject The email subject.
    # @option options [String] to The "to" address.
    # @option options [String] from The "from" address, optional, if blank use default from settings.
    # @option options [String] template The template file to be loaded, optional.
    # @param [Function] callback Callback (err, result) when message is sent or fails.
    send: (options, callback) =>
        logger.debug "Mailer.send", options

        if not @checkConfig()
            errMsg = "SMTP transport wasn't initiated. Abort!"
            logger.warn "Mailer.send", errMsg, options
            callback errMsg, null if callback?
            return

        # Make sure "to" address is valid.
        if not options.to? or options.to is false or options.to is ""
            errMsg = "Option 'to' is not valid. Abort!"
            logger.warn "Mailer.send", errMsg, options
            callback errMsg, null if callback?
            return

        # Set from to default address if no `to` was set, and `logError` defaults to true.
        options.from = "#{settings.general.appTitle} <#{settings.mailer.from}>" if not options.from?
        options.logError = true if not options.logError?

        # Set HTML to body, if passed.
        html = if options.body? then options.body.toString() else ""

        # Get the name of recipient based on the `to` option.
        if options.to.indexOf("<") < 3
            toName = options.to
        else
            toName = options.to.substring 0, options.to.indexOf("<") - 1

        # Load template if a `template` was passed.
        if options.template? and options.template isnt ""
            template = @getTemplate options.template
            html = @parseTemplate template, {contents: html}

            # Parse template keywords if a `keywords` was passed.
            if lodash.isObject options.keywords
                html = @parseTemplate html, options.keywords

        # Parse final template and set it on the `options`.
        html = @parseTemplate html, {to: toName, appTitle: settings.general.appTitle, appUrl: settings.general.appUrl}
        options.html = html

        # Check if `doNotSend` flag is set, and if so, do not send anything.
        if settings.mailer.doNotSend
            callback null, "The 'doNotSend' setting is true, will not send anything!" if callback?
            return

        # Send using the main SMTP. If failed and a secondary is also set, try using the secondary.
        smtpSend smtp, options, (err, result) ->
            if err?
                if smtp2?
                    smtpSend smtp2, options, (err2, result2) -> callback err2, result2
                else
                    callback err, result if callback?
            else
                callback err, result if callback?

    # TEMPLATES
    # --------------------------------------------------------------------------

    # Load and return the specified template. Get from the cache or from the disk
    # if it wasn't loaded yet. Templates are stored inside the `/emailtemplates`
    # folder by default and should have a .html extension. The base template,
    # which is always loaded first, should be called base.html by default.
    # The contents will be inserted on the {contents} tag.
    # @param [String] name The template name, without .html.
    # @return [String] The template HTML.
    getTemplate: (name) =>
        name = name.replace(".html", "") if name.indexOf(".html")

        cached = templateCache[name]

        # Is it already cached? If so do not hit the disk.
        if cached? and cached.expires > moment()
            logger.debug "Mailer.getTemplate", name, "Loaded from cache."
            return templateCache[name].template
        else
            logger.debug "Mailer.getTemplate", name

        # Set file system reading options.
        readOptions = {encoding: settings.general.encoding}
        baseFile = utils.getFilePath path.join(settings.path.emailTemplatesDir, settings.mailer.baseTemplateFile)
        templateFile = utils.getFilePath path.join(settings.path.emailTemplatesDir, "#{name}.html")

        # Read base and `name` template and merge them together.
        base = fs.readFileSync baseFile, readOptions
        template = fs.readFileSync templateFile, readOptions
        result = @parseTemplate base, {contents: template}

        # Save to cache.
        templateCache[name] = {}
        templateCache[name].template = result
        templateCache[name].expires = moment().add "s", settings.general.ioCacheTimeout

        return result

    # Parse the specified template to replace keywords. The `keywords` is a set of key-values
    # to be replaced. For example if keywords is `{id: 1, friendlyUrl: "abc"}` then the tags
    # `{id}` and `{friendlyUrl}` will be replaced with the values 1 and abc.
    # @param [String] template The template (its value, not its name!) to be parsed.
    # @param [Object] keywords Object with keys to be replaced with its values.
    # @return [String] The parsed template, keywords replaced with values.
    parseTemplate: (template, keywords) =>
        template = template.toString()

        for key, value of keywords
            template = template.replace new RegExp("\\{#{key}\\}", "gi"), value

        return template

    # HELPER METHODS
    # --------------------------------------------------------------------------

    # Helper to send emails using the specified transport and options.
    smtpSend = (transport, options, callback) ->
        try
            transport.sendMail options, (err, result) ->
                if err?
                    if options.logError
                        logger.error "Mailer.smtpSend", transport.host, "Could not send: #{options.subject} to #{options.to}.", err
                else
                    logger.debug "Mailer.smtpSend", "OK", transport.host, options.subject, "to #{options.to}", "from #{options.from}."
                callback err, result
        catch ex
            callback ex

    # Helper to create a SMTP object.
    createSmtp = (options) ->
        options.debug = settings.general.debug if not options.debug?
        options.secureConnection = options.secure if not options.secureConnection?

        # Make sure auth is properly set.
        if not options.auth? and options.user? and options.password?
            options.auth = {user: options.user, pass: options.password}
            delete options["user"]
            delete options["password"]

        # Check if `service` is set. If so, pass to the mailer, otheriwse use SMTP.
        if options.service? and options.service isnt ""
            logger.info "Mailer.createSmtp", "Service", options.service
            result = mailer.createTransport options.service, options
        else
            logger.info "Mailer.createSmtp", options.host, options.port, options.secureConnection
            result = mailer.createTransport "SMTP", options

        # Sign using DKIM?
        result.useDKIM settings.mailer.dkim if settings.mailer.dkim.enabled

        # Return SMTP object.
        return result

    # Use the specified options and create a new SMTP server.
    # @param [Object] options Options to be passed to SMTP creator.
    # @param [Boolean] secondary If false set as the main SMTP server, if true set as secondary.
    setSmtp: (options, secondary) =>
        if secondary or secondary > 0
            smtp2 = createSmtp options
        else
            smtp = createSmtp options

    # Force clear the templates cache.
    clearCache: =>
        count = Object.keys(templateCache).length
        templateCache = {}
        logger.info "Mailer.clearCache", "Cleared #{count} templates."


# Singleton implementation
# --------------------------------------------------------------------------
Mailer.getInstance = ->
    @instance = new Mailer() if not @instance?
    return @instance

module.exports = exports = Mailer.getInstance()
