#!/usr/bin/python

import argparse
import configparser
import time
from distutils.util import strtobool
import os.path
import sys

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions


class SetCamApp(object):
    RC_SUCCESS = 0
    RC_FAILURE = 1

    def __init__(self):
        self.configure_argparse()

    def configure_argparse(self):
        a = argparse.ArgumentParser()
        boolhelp = '''If set, enables or disables {0}. By default, the setting
        for {0} will not be changed.'''
        boolmetavar = 'on/off'
        a.add_argument(
            '--camera', dest='camera_hostnames', action='append', default=['*'],
            required=False,
            help='''The hostname or IP address of the camera. May be specified
            multiple times to operate on multiple cameras. Specify * for all
            cameras. Defaults to *.'''
        )
        a.add_argument(
            '-c', '--config-path', dest='config_path', required=True,
            help='The path to a file containing credential configuration.'
        )
        a.add_argument(
            '--motion-events', dest='motion_events', default=None,
            type=strtobool, metavar=boolmetavar,
            help=boolhelp.format('motion events')
        )
        a.add_argument(
            '--continuous-recording', dest='continuous_recording', default=None,
            type=strtobool, metavar=boolmetavar,
            help=boolhelp.format('continuous recording')
        )
        self.argparser = a

    def get_camera_list(self):
        return self.config.sections()

    def get_password(self, hostname):
        return self.config[hostname]['password']

    def run(self, argv):
        args = self.argparser.parse_args(argv)
        self.config = configparser.ConfigParser()
        self.config.read(args.config_path)
        camera_list = args.camera_hostnames
        rc = self.RC_SUCCESS
        errmsg = ''
        if '*' in camera_list:
            camera_list = self.get_camera_list()
        for hostname in camera_list:
            try:
                password = self.get_password(hostname)
                camera = Samsung_SNHP6410BN(hostname)
                camera.login(password)
                camera.set(
                    motion_events=args.motion_events,
                    continuous_recording=args.continuous_recording
                )
            except ConfigurationFailedException as e:
                errmsg += ('Configuration did not change on camera {0}? '
                           'Maybe a camera glitch.\n').format(hostname)
                rc = self.RC_FAILURE
            except Exception as e:
                errmsg += 'Failed while setting camera options on {0}: {1}\n'\
                    .format(hostname, str(e))
                rc = self.RC_FAILURE
            camera.logout()
        if rc != self.RC_SUCCESS:
            print(errmsg.rstrip(), file=sys.stderr)
        return rc


class Samsung_SNHP6410BN(object):
    def __init__(self, hostname, timeout=5):
        self.hostname = hostname
        self._driver = webdriver.PhantomJS(service_log_path=os.path.devnull)
        # For boolean controls, the value is a tuple containing the element
        # names to click for (on, off).
        self._bool_element_ids = {
            'motion_events': ('ea1', 'ea2'),
            # These elements actually control sdcard mounting since there isn't
            # an option directly on the web interface to control continuous
            # recording
            'continuous_recording': ('sdcard_mnt1', 'sdcard_mnt2')
        }
        self.timeout = timeout

    def _get_setup_page(self):
        self._driver.get(
            'http://{}/pages/page_setup_video.php?login=true'
            .format(self.hostname)
        )
        WebDriverWait(self._driver, self.timeout,).until(
            expected_conditions.presence_of_all_elements_located
        )

    def getbool(self, element_ids):
        self._get_setup_page()
        on_element = element_ids[0]
        return self._driver.find_element_by_id(on_element).is_selected()

    def get(self):
        values = dict()
        for k, v in self._bool_element_ids.items():
            values[k] = self.getbool(v)
        return values

    def login(self, password):
        self._driver.get('http://{}'.format(self.hostname))
        WebDriverWait(self._driver, self.timeout,).until(
            expected_conditions.presence_of_all_elements_located
        )
        # Frames?! Who wrote this trash?
        self._driver.switch_to_frame('mainFrame')
        password_field = self._driver.find_element(
            by=By.ID, value='private_key'
        )
        password_field.send_keys(password)
        # The submit button doesn't have an ID. :-/
        submit_button = self._driver.find_element(
            by=By.CLASS_NAME, value='btn_ty2'
        )
        submit_button.click()

    def logout(self):
        self._driver.close()

    def set(self, motion_events=None, continuous_recording=None, verify=True):
        args = locals()
        options = {
            o: args[o] for o in args
            if o not in ('self', 'args', 'verify')
        }
        self._get_setup_page()
        for option in options:
            if options[option] is not None:
                self.setbool(self._bool_element_ids[option], options[option])
        savebutton = self._driver.find_element(by=By.CLASS_NAME, value='bType1')
        savebutton.click()
        if verify:
            # HACK: We need to wait a little bit for the settings to be saved.
            #
            # Can we use WebDriverWait to wait until the form is submitted?
            time.sleep(1)
            values = self.get()
            for option in [o for o in options]:
                # Skip checking options that we did not set
                if options[option] is None:
                    continue
                if options[option] != values[option]:
                    raise ConfigurationFailedException(
                        'Option {0} not set'.format(option)
                    )

    def setbool(self, element_ids, setting):
        if setting:
            element_id = element_ids[0]
        else:
            element_id = element_ids[1]
        element = self._driver.find_element(by=By.ID, value=element_id)
        element.click()

class ConfigurationFailedException(Exception): pass

if __name__ == '__main__':
    exit(SetCamApp().run(sys.argv[1:]))
