#!/usr/bin/env python
# encoding: utf-8
#
# pmatic - Python API for Homematic. Easy to use.
# Copyright (C) 2016 Lars Michelsen <lm@larsmichelsen.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

"""A daemon for managing pmatic scripts

The pmatic manager is used to manage pmatic scripts on the local system. The
main purpose for this manager is to make the pmatic scripts on the CCU be
manageable using a web GUI. However, it is possible to run the pmatic manager
on any linux system of your choice. This might have the advantage that the
manager and scripts are performing better than running it directly on the CCU.

The tasks of the manager:

* Make it possible to upload and delete scripts

  You are completely free to upload, delete or modify the scripts using other
  ways, for example via SSH at any time.

* Run the scripts for testing

  This is also possible via SSH but more comfortable via your browser using
  the pmatic manager.

* Invoke the scripts based on your configuration

  The pmatic manager can execute scripts of your choice when specific events
  occur like a value changed or based on dates / times.
"""

# Add Python 3.x behaviour to 2.7
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals

import sys
import traceback
import socket
import argparse
import logging
from logging.handlers import RotatingFileHandler

import pmatic.manager
from pmatic.exceptions import SignalReceived

pmatic.manager.Config.load()

def parse_args():
    parser = argparse.ArgumentParser()
    parser.add_argument('-a',
        dest="address",
        default="",
        help="Specify another host address than the default (all) to listen on.")
    parser.add_argument('-p',
        dest="port",
        default="9120",
        type=int,
        help="Specify another TCP port than the default one (9120) to listen on.")
    parser.add_argument('-g',
        dest="foreground",
        action="store_true",
        help="Execute the pmatic manager in foreground instead of daemonizing.")
    parser.add_argument('-o',
        dest="config_path",
        help="Specify the location to the pmatic Manager config directory. "
             "The default directory is \"/etc/config/addons/pmatic/etc\".",
        default=pmatic.manager.Config.config_path)
    parser.add_argument('-e',
        dest="state_path",
        help="Specify the location to the pmatic Manager state directory. "
             "The default directory is \"/var/lib/pmatic\".",
        default=pmatic.manager.Config.state_path)
    parser.add_argument('-r',
        dest="script_path",
        help="Specify the location to the pmatic script directory. "
             "The default directory is \"/etc/config/addons/pmatic/scripts\".",
        default=pmatic.manager.Config.script_path)
    parser.add_argument('-t',
        dest="static_path",
        help="Specify the location to the pmatic static file (images, ...) directory. "
             "The default directory is \"/etc/config/addons/pmatic/manager_static\".",
        default=pmatic.manager.Config.static_path)
    parser.add_argument('-l',
        dest="log_level",
        help="Specify the log level (defaults to INFO). You can choose from the following: "
             "CRITICAL, ERROR, WARNING, INFO, DEBUG.",
        default=pmatic.manager.Config.log_level or "INFO",
        choices=pmatic.log_level_names)
    parser.add_argument('-f',
        dest="log_file",
        help="Configure the location of the file to write log entries to. When "
             "the log file reaches a size of ~5MB, it is rotated to LOG_FILE.1, "
             "then to LOG_FILE.2 and then deleted. The default log file is "
             "\"/var/log/pmatic-manager.log\".",
        default=pmatic.manager.Config.log_file)
    parser.add_argument('-c',
        dest="ccu_address",
        help="When the pmatic manager is executed on another host than the CCU, "
             "you need to configure the address and credentials to access the "
             "CCU. When executed on the CCU, these options are ignored.",
        default=pmatic.manager.Config.ccu_address)
    parser.add_argument('-u',
        dest="ccu_username",
        help="When the pmatic manager is executed on another host than the CCU, "
             "you need to configure the username to access the CCU. When executed "
             "on the CCU, these options are ignored.",
        default=pmatic.manager.Config.ccu_credentials[0]
                    if pmatic.manager.Config.ccu_credentials else "")
    parser.add_argument('-s',
        dest="ccu_password",
        help="When the pmatic manager is executed on another host than the CCU, "
             "you need to configure the password of the given username to access "
             "the CCU. When executed on the CCU, these options are ignored.",
        default=pmatic.manager.Config.ccu_credentials[1]
                    if pmatic.manager.Config.ccu_credentials else "")
    parser.add_argument('-n',
        dest="ccu_enabled",
        action="store_false",
        help="Disable the connection with the CCU. This may be useful when "
             "testing/using the pmatic Manager without a CCU.",
        default=pmatic.manager.Config.ccu_enabled)

    args = parser.parse_args()
    return args

args = parse_args()

# If the config_path changed, load the new config. Then load the args
# again because the new config might have changed the arg defaults
if pmatic.manager.Config.config_path != args.config_path:
    pmatic.manager.Config.config_path = args.config_path
    pmatic.manager.Config.load()
    args = parse_args()

# Set log level according to user supplied arguments
pmatic.logging(getattr(pmatic, args.log_level))

# auto rotate on 5MB, rotate files 2 times, then overwrite
logger = logging.getLogger("pmatic")
pmatic.manager.Config.log_file = args.log_file
file_logger = RotatingFileHandler(args.log_file, maxBytes=5*1024*1024, backupCount=2)
file_logger.setLevel(args.log_level)
file_logger.setFormatter(logging.Formatter('%(asctime)s [%(levelname)s] %(message)s'))

# Apply other paths
pmatic.manager.Config.script_path = args.script_path
pmatic.manager.Config.static_path = args.static_path
pmatic.manager.Config.state_path  = args.state_path

logger.addHandler(file_logger)

# Set CCU address / credentials when provided by user to override the configuration
pmatic.manager.Config.ccu_enabled = args.ccu_enabled
pmatic.manager.Config.ccu_address = args.ccu_address
if args.ccu_username and args.ccu_password:
    pmatic.manager.Config.ccu_credentials = args.ccu_username, args.ccu_password

logger.info("------------------------------------------")
logger.info("Starting up (Version %s)" % pmatic.__version__)
logger.info("------------------------------------------")


try:
    manager = pmatic.manager.Manager((args.address, args.port))
except socket.error as e:
    if e.errno == 98:
        sys.stderr.write("Failed to start. Maybe another pmatic manager process is already "
                         "running?\n (%s)\n" % e)
        sys.exit(1)
    else:
        raise

if not args.foreground:
    manager.daemonize()

manager.init_scheduler()
manager.register_signal_handlers()
manager.init_ccu()

try:
    manager.logger.info("Start listening on TCP %d..." % args.port)
    manager.serve_forever()
except SignalReceived as e:
    manager.logger.info("Killed with signal %d." % e._signum)
    sys.exit(1)
except Exception as e:
    manager.logger.error("Unhandled exception: %s" % traceback.format_exc())
    sys.exit(1)
