#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
Auxiliary script used to allocate memory on guests.

:copyright: 2008-2009 Red Hat Inc.
:author: Jiri Zupka (jzupka@redhat.com)
"""

from __future__ import division
import os
import array
import sys
import random
import copy
import tempfile
import datetime
import math

from six.moves import input


PAGE_SIZE = 4096  # machine page size

TMPFS_OVERHEAD = 0.0022  # overhead on 1MB of write data


class MemFill(object):

    """
    Fills guest memory according to certain patterns.
    """

    def __init__(self, mem, static_value, random_key):
        """
        Constructor of MemFill class.

        :param mem: Amount of test memory in MB.
        :param random_key: Seed of random series used for fill up memory.
        :param static_value: Value used to fill all memory.
        """
        if (static_value < 0 or static_value > 255):
            print("FAIL: Initialization static value"
                  "can be only in range (0..255)")
            return

        self.tmpdp = tempfile.mkdtemp()
        tmpfs_size = mem + math.ceil(mem * TMPFS_OVERHEAD)
        ret_code = os.system("mount -o size=%dM tmpfs %s -t tmpfs" %
                             (tmpfs_size, self.tmpdp))
        if ret_code != 0:
            if os.getuid() != 0:
                print("FAIL: Unable to mount tmpfs "
                      "(likely cause: you are not root)")
            else:
                print("FAIL: Unable to mount tmpfs")
        else:
            self.f = tempfile.TemporaryFile(prefix='mem', dir=self.tmpdp)
            self.allocate_by = 'L'
            self.npages = ((mem * 1024 * 1024) // PAGE_SIZE)
            self.random_key = random_key
            self.static_value = static_value
            print("PASS: Initialization (tmpfs size: %dM)" % tmpfs_size)

    def __del__(self):
        if os.path.ismount(self.tmpdp):
            self.f.close()
            os.system("umount %s" % (self.tmpdp))

    def compare_page(self, original, inmem):
        """
        Compare pages of memory and print the differences found.

        :param original: Data that was expected to be in memory.
        :param inmem: Data in memory.
        """
        for ip in range(PAGE_SIZE // original.itemsize):
            if (not original[ip] == inmem[ip]):  # find which item is wrong
                originalp = array.array("B")
                inmemp = array.array("B")
                originalp.fromstring(original[ip:ip + 1].tostring())
                inmemp.fromstring(inmem[ip:ip + 1].tostring())
                for ib in range(len(originalp)):  # find wrong byte in item
                    if not (originalp[ib] == inmemp[ib]):
                        position = (self.f.tell() - PAGE_SIZE + ip *
                                    original.itemsize + ib)
                        print("Mem error on position %d wanted 0x%Lx and is "
                              "0x%Lx" % (position, originalp[ib], inmemp[ib]))

    def value_page(self, value):
        """
        Create page filled by value.

        :param value: String we want to fill the page with.
        :return: return array of bytes size PAGE_SIZE.
        """
        a = array.array("B")
        for _ in range((PAGE_SIZE // a.itemsize)):
            try:
                a.append(value)
            except Exception:
                print("FAIL: Value can be only in range (0..255)")
        return a

    def random_page(self, seed):
        """
        Create page filled by static random series.

        :param seed: Seed of random series.
        :return: Static random array series.
        """
        random.seed(seed)
        a = array.array(self.allocate_by)
        for _ in range(PAGE_SIZE // a.itemsize):
            a.append(random.randrange(0, sys.maxsize))
        return a

    def value_fill(self, value=None):
        """
        Fill memory page by page, with value generated with value_page.

        :param value: Parameter to be passed to value_page. None to just use
                what's on the attribute static_value.
        """
        self.f.seek(0)
        if value is None:
            value = self.static_value
        page = self.value_page(value)
        for _ in range(self.npages):
            page.tofile(self.f)
        print("PASS: Mem value fill")

    def value_check(self, value=None):
        """
        Check memory to see if data is correct.

        :param value: Parameter to be passed to value_page. None to just use
                what's on the attribute static_value.
        :return: if data in memory is correct return PASS
                else print some wrong data and return FAIL
        """
        self.f.seek(0)
        e = 2
        failure = False
        if value is None:
            value = self.static_value
        page = self.value_page(value)
        for _ in range(self.npages):
            pf = array.array("B")
            pf.fromfile(self.f, PAGE_SIZE // pf.itemsize)
            if not (page == pf):
                failure = True
                self.compare_page(page, pf)
                e = e - 1
                if e == 0:
                    break
        if failure:
            print("FAIL: value verification")
        else:
            print("PASS: value verification")

    def static_random_fill(self, n_bytes_on_end=PAGE_SIZE):
        """
        Fill memory by page with static random series with added special value
        on random place in pages.

        :param n_bytes_on_end: how many bytes on the end of page can be changed.
        :return: PASS.
        """
        self.f.seek(0)
        page = self.random_page(self.random_key)
        random.seed(self.random_key)
        p = copy.copy(page)

        t_start = datetime.datetime.now()
        for pages in range(self.npages):
            rand = random.randint(((PAGE_SIZE // page.itemsize) - 1) -
                                  (n_bytes_on_end // page.itemsize),
                                  (PAGE_SIZE // page.itemsize) - 1)
            p[rand] = pages
            p.tofile(self.f)
            p[rand] = page[rand]

        t_end = datetime.datetime.now()
        delta = t_end - t_start
        milisec = delta.microseconds // 1e3 + delta.seconds * 1e3
        print("PASS: filling duration = %Ld ms" % milisec)

    def static_random_verify(self, n_bytes_on_end=PAGE_SIZE):
        """
        Check memory to see if it contains correct contents.

        :return: if data in memory is correct return PASS
                else print some wrong data and return FAIL.
        """
        self.f.seek(0)
        e = 2
        page = self.random_page(self.random_key)
        random.seed(self.random_key)
        p = copy.copy(page)
        failure = False
        for pages in range(self.npages):
            rand = random.randint(((PAGE_SIZE // page.itemsize) - 1) -
                                  (n_bytes_on_end // page.itemsize),
                                  (PAGE_SIZE // page.itemsize) - 1)
            p[rand] = pages
            pf = array.array(self.allocate_by)
            pf.fromfile(self.f, PAGE_SIZE // pf.itemsize)
            if not (p == pf):
                failure = True
                self.compare_page(p, pf)
                e = e - 1
                if e == 0:
                    break
            p[rand] = page[rand]
        if failure:
            print("FAIL: Random series verification")
        else:
            print("PASS: Random series verification")


def die():
    """
    Quit allocator.
    """
    sys.exit(0)


def main():
    """
    Main (infinite) loop of allocator.
    """
    print("PASS: Start")
    end = False
    while not end:
        sr = input()
        exec(sr)


if __name__ == "__main__":
    main()
