from __future__ import print_function
from __future__ import absolute_import
# Written by Ross Cohen
# Maintained by Chris Hutchinson
# see LICENSE.txt for license information

from builtins import object
import binascii
from .entropy import random_string
from os import chmod, fdopen, path, remove, rename, stat
from . import SRP
from sys import platform
from tempfile import mkstemp

if platform != 'win32':
    from os import lchown

class Passwd(object):
    def __init__(self, pw_file, create=False, uid=-1):
        self.users = {}
        self.pw_file = pw_file
        self.pw_dir = path.split(pw_file)[0]
        self.modtime = 0
        if create:
            h = open(self.pw_file, 'a')
            if uid != -1:
                lchown(self.pw_file, uid, -1)
            chmod(self.pw_file, 0o600)
            h.close()
        self._read()

    def get(self, user):
        self._read()
        if user not in self.users:
            raise KeyError
        info = self.users[user]
        if 'secret' not in info:
            info['secret'] = random_string(20)
        return info

    def add(self, user, password):
        self._read()
        if user in self.users:
            raise ValueError('User exists')
        s, v = SRP.new_passwd(user, password)
        self.users[user] = {'s': s, 'v': v}
        self._dump()

    def set(self, user, password):
        self._read()
        if user not in self.users:
            raise ValueError('No such user')
        s, v = SRP.new_passwd(user, password)
        self.users[user] = {'s': s, 'v': v}
        self._dump()

    def define(self, user, v, s):
        self._read()
        if user not in self.users:
            raise ValueError('No such user')
        self.users[user] = {'s': s, 'v': v}
        self._dump()

    def delete(self, user):
        self._read()
        try:
            del self.users[user]
        except KeyError:
            raise ValueError('No such user')
        self._dump()

    def _read(self):
        h = open(self.pw_file, 'rU')

        modtime = path.getmtime(self.pw_file)
        if self.modtime == modtime:
            return
        self.modtime = modtime

        l, used = None, {}
        while l != '':
            l = h.readline()
            if l.strip() == '':
                continue

            try:
                user, salt, verifier, garbage = l.split(':', 3)
                salt = binascii.unhexlify(salt)
            except ValueError:
                print('garbage in passwd file')
                raise ValueError

            if user not in self.users:
                self.users[user] = {'s': salt, 'v': int(verifier)}
            else:
                self.users[user]['s'] = salt
                self.users[user]['v'] = int(verifier)

            used[user] = 1

        h.close()

        # remove the deleted entries
        for user in list(self.users.keys()):
            if user not in used:
                del self.users[user]

        self.modtime = path.getmtime(self.pw_file)

    def _dump(self):
        text = []
        for user, pw in list(self.users.items()):
            text.append('%s:%s:%d:\n' % (user, binascii.hexlify(pw['s']), pw['v']))
        text.sort()

        fd, fname = mkstemp('', 'cdv', self.pw_dir)
        h = fdopen(fd, 'w')
        h.writelines(text)
        h.close()

        if platform != 'win32':
            statinfo = stat(self.pw_file)
            lchown(fname, statinfo.st_uid, statinfo.st_gid)
        chmod(fname, 0o600)
        if platform == 'win32':
            remove(self.pw_file)
        rename(fname, self.pw_file)
        self.modtime = path.getmtime(self.pw_file)
