from traceback import format_exc

from django.http import HttpResponse, HttpResponseRedirect, Http404
from django.views.generic.base import View
from django.shortcuts import render_to_response, render, get_object_or_404
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger

from django.contrib import auth
from django.core.urlresolvers import reverse_lazy
from django.conf import settings

import datetime
import time
from pprint import pprint, pformat

import models

class ActionGroup(object):
    def __init__(self, action, entries):
        self.action = action
        self.entries = entries or []

def index(request):
    # Get statistical items: total-count, first entry, last entry
    num_entries = models.LogEntry.objects.count()

    entry_first = models.LogEntry.objects.order_by("date_entry")[0] if num_entries else None
    entry_most_recent = models.LogEntry.objects.order_by("-date_entry")[0] if num_entries else None

    # Get number of consecutive days with entries
    consecutive_entries = 0
    date_cursor = datetime.date.today()
    for entry in models.LogEntry.objects.order_by("-date_entry"):
        if entry.date_entry > date_cursor:
            continue
        if entry.date_entry == date_cursor:
            consecutive_entries += 1
            date_cursor -= datetime.timedelta(days=1)
        else:
            break

    # Get items for this month
    today = datetime.date.today()
    first_this_month = datetime.date(today.year, today.month, 1)
    entries = list(models.LogEntry.objects.filter(date_entry__gte=first_this_month).order_by("-date_entry"))

    # Sort entries into action groups
    groups = {}
    for entry in entries:
        if entry.action.name in groups:
            groups[entry.action.name].append(entry)
        else:
            groups[entry.action.name] = [entry]

    return render(request, 'index.html', {
        "entry_first": entry_first,
        "entry_most_recent": entry_most_recent,
        "entry_count": num_entries,
        "consecutive_entries": consecutive_entries,
        "entries": entries,
        "num_entries_this_month": len(entries),
        "num_days_without_entries_this_month": today.day - len(entries),
        "groups": groups,
    })


from dataclasses import dataclass
from enum import Enum
import struct
from anpp_packets.an_packets import PacketID
from anpp_packets.an_packet_protocol import ANPacket

class OffsetType(Enum):
    """Offset Type"""

    manual_offset = 0
    automatic_offset = 1

class AutomaticOffsetOrientation(Enum):
    """Automatic Offset Orientation"""

    primary_front_secondary_rear = 0
    primary_read_secondary_front = 1
    primary_right_secondary_left = 2
    primary_left_secondary_right = 3

@dataclass()
class DualAntennaConfigurationPacket:
    """Packet 196 - Dual Antenna Configuration Packet"""

    permanent: int = 0
    offset_type: OffsetType = OffsetType.manual_offset
    automatic_offset_orientation: AutomaticOffsetOrientation = (
        AutomaticOffsetOrientation.primary_front_secondary_rear
    )
    manual_offset_x: float = 0
    manual_offset_y: float = 0
    manual_offset_z: float = 0

    ID = PacketID.dual_antenna_configuration
    LENGTH = 17

    _structure = struct.Struct("<BHBxfff")

    def decode(self, an_packet: ANPacket) -> int:
        """Decode ANPacket to Dual Antenna Configuration Packet
        Returns 0 on success and 1 on failure"""
        if (an_packet.id == self.ID) and (len(an_packet.data) == self.LENGTH):
            values = self._structure.unpack_from(an_packet.data)
            self.permanent = values[0]
            self.offset_type = OffsetType(values[1])
            self.automatic_offset_orientation = AutomaticOffsetOrientation(values[2])
            self.manual_offset_x = values[3]
            self.manual_offset_y = values[4]
            self.manual_offset_z = values[5]
            return 0
        else:
            return 1

    def encode(self) -> ANPacket:
        """Encode Dual Antenna Configuration Packet to ANPacket
        Returns the ANPacket"""
        data = self._structure.pack(
            self.permanent,
            self.offset_type.value,
            self.automatic_offset_orientation.value,
            self.manual_offset_x,
            self.manual_offset_y,
            self.manual_offset_z,
        )

        an_packet = ANPacket()
        an_packet.encode(self.ID, self.LENGTH, data)

        return an_packet


import paho.mqtt.client as mqtt
import json

def on_connect(client, userdata, flags, rc):
    if rc == 0:
        print("connected OK")
    else:
        print("Bad connection Returned code=", rc)

def on_disconnect(client, userdata, flags, rc=0):
    print(str(rc))

def on_subscribe(client, userdata, mid, granted_qos):
    print("subscribed: " + str(mid) + " " + str(granted_qos))

def on_message(client, userdata, msg):
    parsed_data = json.loads(msg.payload.decode("utf-8"))
    print(parsed_data['data'])

    EQIP_ID = 1
    EQIP_ID_CON = 1234
    Current_TEMP = 36.5
    Contect_TEMP_01=36.5
    Contect_TEMP_02=36.5
    Contect_TEMP_03=36.5
    Contect_TEMP_04=36.5
    Contect_TEMP_05=36.5
    CPU_TEMP = 36.5
    Spark = False
    Smoke = False
    Fire = False
    Fire_ext = False
    Sensor_Alarm = False
    Cpu_Alarm = False
    Comm_Alarm = False
    Fire_ext_Controll = False

# 새로운 클라이언트 생성
client = mqtt.Client()
# 콜백 함수 설정 on_connect(브로커에 접속), on_disconnect(브로커에 접속중료), on_subscribe(topic 구독),
# on_message(발행된 메세지가 들어왔을 때)
client.on_connect = on_connect
client.on_disconnect = on_disconnect
client.on_subscribe = on_subscribe
client.on_message = on_message

client.username_pw_set("modules", "12345678")

# address : localhost, port: 1883 에 연결
client.connect('127.0.0.1', 1883)

client.subscribe('application/1/device/ac1f09fffe0b8a54/rx', 1)
client.loop_forever()


from flask import render_template, session, redirect, url_for, current_app, request

from . import main
from .forms import lcd_set_form

import logging

format = "%(asctime)s: %(message)s"
logging.basicConfig(format=format, level=logging.INFO,
                    datefmt="%H:%M:%S")

from .. import lcd_state, task_queue
# from .. import lcd_state

def post_msg_to_queue(msg):
    global task_queue

    logging.info(f"View - posting message: {msg}")
    task_queue.put(msg)

@main.route('/')
def index():
    post_msg_to_queue({'action': "test"})
    return render_template('index.html')

@main.route('/lcd/msg')
def lcd_message():
    return render_template('lcd.html', lines=lcd_state['msg'], local_hardware=current_app.config['LOCAL_HARDWARE'])

@main.route('/lcd/clear')
def lcd_clear_message():

    lcd_state['msg'] = {'line1': '', 'line2': '', 'line3': '', 'line4': ''}

    post_msg_to_queue({'action': "redisplay"})

    return render_template('lcd.html', msg=lcd_state['msg'], local_hardware=current_app.config['LOCAL_HARDWARE'])

@main.route('/lcd/set', methods=['GET', 'POST'])
def lcd_set_message():

    form = lcd_set_form(request.form)

    print(f"Request Methon: {request.method}")
    if request.method == 'POST':
        print(f"  Valid form? {form.validate()}")

        if form.validate():
            print("Processing submitted form data...")
            lcd_state['msg']['line1'] = form.line1.data
            lcd_state['msg']['line2'] = form.line2.data
            lcd_state['msg']['line3'] = form.line3.data
            lcd_state['msg']['line4'] = form.line4.data

            # lcd_string(lcd_state['msg']['line1'], 1)
            # lcd_string(lcd_state['msg']['line2'], 2)
            # lcd_string(lcd_state['msg']['line3'], 3)
            # lcd_string(lcd_state['msg']['line4'], 4)

            post_msg_to_queue({'action': "redisplay"})

            print("redirecting to GET display resource...")
            return redirect(url_for('main.lcd_message'))
        else:
            return render_template('lcd_set.html', form=form, local_hardware=current_app.config['LOCAL_HARDWARE'])

    else:  # was a GET message
        form = lcd_set_form()

        print("Displaying form for user data entry`...")
        form.line1.data = lcd_state['msg']['line1']
        form.line2.data = lcd_state['msg']['line2']
        form.line3.data = lcd_state['msg']['line3']
        form.line4.data = lcd_state['msg']['line4']
        return render_template('lcd_set.html', form=form)


import pytest
from selenium import webdriver
from config import *

# c_path = r"C:\Users\AS064243\OneDrive - Cerner Corporation\Documents\Learning\pytestframework\pytestframework\drivers\chromedriver.exe"

class DriverInit:

    @pytest.fixture(scope="class", autouse=True)
    def driver_Init(self, request):
        if browser.lower() == "chrome":
            driver = webdriver.Chrome(executable_path=c_path)

        elif browser.lower() == "firefox":
            driver = webdriver.Firefox(executable_path=f_path)

        driver.get(url)
        driver.maximize_window()
        driver.maximize_window()
        request.cls.driver = driver

        yield
        driver.quit()


import scrapy
import pandas as pd
from arabic_scrapper.helper import load_dataset_lists,selenium_path
from arabic_scrapper.items import GeneralItem
import time
from bs4 import BeautifulSoup
import requests
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from lxml import etree
from datetime import datetime

site_list,catagory,main_category,sub_category,platform,media_type,urgency = load_dataset_lists("Diwan crown prince state of kuwait")
now = datetime.now()

class DiwanSpider(scrapy.Spider):
    name = 'diwan'
    def __init__(self):
        self.chrome_options = Options()
        self.chrome_options.add_argument("--headless")
        self.chrome_options.add_argument('--no-sandbox')
        self.chrome_options.add_argument('--disable-dev-shm-usage')
        self.chromedriver=selenium_path()

    def start_requests(self):
        for page,catagori,main_categor,sub_categor,platfor,media_typ,urgenc in zip(site_list,catagory,main_category,sub_category,platform,media_type,urgency):
            url = page
            driver = webdriver.Chrome(self.chromedriver,options=self.chrome_options)
            driver.delete_all_cookies()
            driver.get(url)
            # time.sleep(5)
            html=driver.page_source
            driver.quit()
            soup=BeautifulSoup(html,"lxml") #donwloading the entiring page
            #print("////////////////soup////////////",soup)
            dom = etree.HTML(str(soup))
            # #print("//////////////////dom/////////////////",dom)
            urls=dom.xpath('//*[@class="news-texts"]/a/@href')
            #print("///////////////////all urls/////////////////////////",urls)
            for url in urls:
                page_url="https://cpd.gov.kw/Ar/"+url
                #print("/////////////////////pageurl//////////////////",page_url)
                # yield scrapy.Request(url=page_url,callback=self.link_extractor,meta={"current_url":page_url,"catagory":catagori})
                yield scrapy.Request(url=page_url,callback=self.link_extractor,meta={"current_url":page_url,"catagory":catagori,"main_category":main_categor,"sub_category":sub_categor,"platform":platfor,"media_type":media_typ,"urgency":urgenc})

    def link_extractor(self,response):
        #print("/////////////////////////////News page//////////////////////",response.meta["current_url"])
        #download
        driver = webdriver.Chrome(self.chromedriver,options=self.chrome_options)
        driver.delete_all_cookies()
        driver.get(response.meta["current_url"])
        html=driver.page_source
        driver.quit()
        soup=BeautifulSoup(html,"lxml")
        dom = etree.HTML(str(soup))
        print("################html variable###############",html,type(html))# <class 'str'>
        print("########### Soup variable ##############",soup,type(soup)) #<class 'bs4.BeautifulSoup'>
        print("########### dom variable ##############",dom,type(dom))

        contents=dom.xpath('//*[@class="news-texts"]//p/text()')
        contents="\n\n".join(contents[0:len(contents)])

        diwan_item=GeneralItem()
        diwan_item["news_agency_name"]="Diwan crown prince state of kuwait"
        diwan_item["page_url"]=response.meta["current_url"]
        diwan_item["category"]=response.meta["catagory"]
        diwan_item["title"]=str("".join(dom.xpath('//*[@class="news-texts"]/a/h3/text()')))
        diwan_item["contents"]=contents
        diwan_item["image_url"]="https://cpd.gov.kw"+str("".join(dom.xpath('//*[@class="carousel-item active"]/img/@src')))
        diwan_item["date"]=str("".join(dom.xpath('//*[@class="news-texts"]/p[1]/text()')))
        diwan_item["author_name"]="Diwan crown prince state of kuwait"

        diwan_item["main_category"]=response.meta["main_category"]
        diwan_item["sub_category"]=response.meta["sub_category"]
        diwan_item["platform"]=response.meta["platform"]
        diwan_item["media_type"]=response.meta["media_type"]
        diwan_item["urgency"]=response.meta["urgency"]
        diwan_item["created_at"]=str(now.strftime("%Y:%m:%d %H:%M:%S"))
        diwan_item["updated_at"]=str(now.strftime("%Y:%m:%d %H:%M:%S"))
        diwan_item["deleted_at"]=None
        yield diwan_item


# Python driver script that loads an input image from disk and then
# applies FFT blur detection to it.
# USAGE:
# python blur_detector_image.py --image images

# import the necessary packages
from fourier_blur import detect_blur_fft
import numpy as np
import argparse
import imutils
import cv2
import os

# construct the argument parser and parse four command line arguments
    # --image: the path to the input image for blur detection
    # --thresh: the threshold for blur detector calculation
    # --vis: flag indicating whether to visualize the input image
    # and the magnitude spectrum image
    # --test: test results by progressively blurring input image and
    # conduct FFT-based blur detection on each image
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", type=str, required=True,
	help="path input image used to detect blur")
ap.add_argument("-t", "--thresh", type=int, default=20,
	help="threshold for blur detector to fire")
ap.add_argument("-v", "--vis", type=int, default=-1,
	help="whether or not intermediary steps will bev visualized")
ap.add_argument("-d", "--test", type=int, default=-1,
	help="whether or not image should be progressively blurred")
args = vars(ap.parse_args())

# loop over images
for imagePath in os.listdir(args["image"]):
    image = args["image"] + "\\" + imagePath
    print(image)
# load the input image from disk, resize it, and convert it to
# grayscale
    orig = cv2.imread(image)
    orig = imutils.resize(orig, width=500)
    gray = cv2.cvtColor(orig, cv2.COLOR_BGR2GRAY)

    # apply blur detector using the FFT function
    (mean, blurry) = detect_blur_fft(gray, size=60,
        thresh=args["thresh"], vis=args["vis"] > 0)

    # draw on the image, indicating whether or not it is blurry
    # add two more channels to single-channel gray image, storing
    # the result in image
    image = np.dstack([gray] * 3)
    color = (0, 0, 255) if blurry else (0, 255, 0)
    # set color as red(if blurry) and green(if not blurry)
    text = "Blurry ({:.4f})" if blurry else "Not Blurry ({:.4f})"
    # draw blurry text indication and mean value in top-left corner
    # of image, print out same info in terminal
    text = text.format(mean)
    cv2.putText(image, text, (10, 25), cv2.FONT_HERSHEY_SIMPLEX, 0.7,
        color, 2)
    print("[INFO] {}".format(text))

    # show the output image
    # cv2.imshow("Output", image)
    # cv2.waitKey(0)

    # check to see if are going FFT blurriness detector is tested
    # using various sizes of a Gaussian kernel
    if args["test"] > 0:
        # loop over various blur radii
        for radius in range(1, 30, 2):
            # clone the original grayscale image
            image = gray.copy()

            #check to see if the kernel radius is greater than zero
            if radius > 0:
                # blur the input image by the supplied radius using a
                # Gaussian kernel
                image = cv2.GaussianBlur(image, (radius, radius), 0)

                # apply our blur detector using the FFT
                (mean, blurry) = detect_blur_fft(image, size=60,
                    thresh=args["thresh"], vis=args["vis"] > 0)

                # draw on the image, indicating whether or not it is
                # blurry
                image = np.dstack([image] * 3)
                color = (0, 0, 255) if blurry else (0, 255, 0)
                text = "Blurry ({:.4f})" if blurry else "Not Blurry ({:.4f})"
                text = text.format(mean)
                cv2.putText(image, text, (10, 25), cv2.FONT_HERSHEY_SIMPLEX,
                    0.7, color, 2)
                print("[INFO] Kernel: {}, Result: {}".format(radius, text))

            # show the image
            cv2.imshow("Test Image", image)
            cv2.waitKey(0)


import tensorflow as tf
from tensorflow import keras

from keras import initializers
from keras import regularizers
from keras import constraints
from keras import activations

from typing import Optional
from typing import Callable
from typing import Union
from typing import Type

from molgraph.internal import register_keras_serializable

from molgraph.tensors.graph_tensor import GraphTensor

@register_keras_serializable(package='molgraph')
class EdgeConv(tf.keras.layers.Layer):

    '''Edge convolutional layer, used to build DMPNN [#]_ and DGIN [#]_ like models.

    **Important:**

    As of now, EdgeConv only works on (sub)graphs with at least one edge/bond. If your dataset consists
    of molecules with a single atom, please add self loops:
    ``molgraph.chemistry.MolecularGraphEncoder(..., self_loops=True)``

    Example usage:

    >>> graph_tensor = molgraph.GraphTensor(
    ...     sizes=[2, 3],
    ...     node_feature=[[1., 0.], [1., 0.], [1., 0.], [1., 0.], [0., 1.]],
    ...     edge_feature=[[1., 0.], [0., 1.], [0., 1.], [0., 1.],
    ...                   [1., 0.], [0., 1.], [1., 0.], [0., 1.]],
    ...     edge_src=[1, 0, 3, 4, 2, 4, 3, 2],
    ...     edge_dst=[0, 1, 2, 2, 3, 3, 4, 4],
    ... )
    >>> gnn_model = tf.keras.Sequential([
    ...     molgraph.layers.EdgeConv(16),                      # will produce 'edge_state' component
    ...     molgraph.layers.EdgeConv(16),                      # will produce 'edge_state' component
    ...     molgraph.layers.NodeReadout(target='edge_state'),  # target='edge_state' is default
    ...     molgraph.layers.Readout(),
    ... ])
    >>> gnn_model(graph_tensor).shape
    TensorShape([2, 16])

    Args:
        units (int, None):
            Number of output units.
        update_mode (bool):
            Specify what type of update will be performed. Either 'dense' or 'gru'.
            Default to 'gru'.
        update_fn (tf.keras.layers.GRUCell, tf.keras.layers.Dense, None):
            Optionally pass update function (GRUCell or Dense) for weight-tying
            of the update step (GRU step or Dense step). Default to None.
        activation (tf.keras.activations.Activation, callable, str, None):
            Activation function applied to the updated edge states. If None is set, either 'relu'
            (for `update_mode='dense'`) or 'tanh' (for `update_mode='gru'`) will be used.
            Default to None.
        recurrent_activation (tf.keras.activations.Activation, callable, str, None):
            Activation function applied to the recurrent step (only relevant if ``update_mode='gru'``).
            Default to to 'sigmoid'.
        use_bias (bool):
            Whether the layer should use biases. If None is set, either False or True will be used.
            Default to None.
        kernel_initializer (tf.keras.initializers.Initializer, str):
            Initializer function for the kernels. If None is set, either
            ``tf.keras.initializers.TruncatedNormal(stddev=0.005)`` (dense) or ``glorot_uniform`` (gru)
            will be used. Default to None.
        bias_initializer (tf.keras.initializers.Initializer, str):
            Initializer function for the biases. Default to
            tf.keras.initializers.Constant(0.).
        recurrent_initializer (tf.keras.initializers.Initializer, str):
            Initializer function for the recurrent kernel (only relevant if ``update_mode='gru'``).
            Default to 'orthogonal'.
        kernel_regularizer (tf.keras.regularizers.Regularizer, None):
            Regularizer function applied to the kernels. Default to None.
        bias_regularizer (tf.keras.regularizers.Regularizer, None):
            Regularizer function applied to the biases. Default to None.
        activity_regularizer (tf.keras.regularizers.Regularizer, None):
            Regularizer function applied to the final output of the layer.
            Default to None.
        recurrent_regularizer (tf.keras.regularizers.Regularizer, None):
            Regularizer function applied to the recurrent kernel (only relevant if ``update_mode='gru'``).
            Default to None.
        kernel_constraint (tf.keras.constraints.Constraint, None):
            Constraint function applied to the kernels. Default to None.
        bias_constraint (tf.keras.constraints.Constraint, None):
            Constraint function applied to the biases. Default to None.
        recurrent_constraint (tf.keras.constraints.Constraint, None):
            Constraint function applied to the recurrent kernel (only relevant if ``update_mode='gru'``).
            Default to None.

    References:
        .. [#] https://arxiv.org/pdf/1904.01561.pdf
        .. [#] https://www.mdpi.com/1420-3049/26/20/6185
    '''

    def __init__(
        self,
        units: Optional[int],
        update_mode: str = 'dense',
        update_fn: Optional[Union[
            tf.keras.layers.GRUCell, tf.keras.layers.Dense]] = None,
        activation: Union[str, None, Callable[[tf.Tensor], tf.Tensor]] = None,
        recurrent_activation: Union[str, None, Callable[[tf.Tensor], tf.Tensor]] = 'sigmoid',
        use_bias: Optional[bool] = None,
        kernel_initializer: Union[str, None, initializers.Initializer] = None,
        bias_initializer: Union[str, None, initializers.Initializer] = 'zeros',
        recurrent_initializer: Union[str, None, initializers.Initializer] = 'orthogonal',
        kernel_regularizer: Optional[regularizers.Regularizer] = None,
        bias_regularizer: Optional[regularizers.Regularizer] = None,
        recurrent_regularizer: Optional[regularizers.Regularizer] = None,
        activity_regularizer: Optional[regularizers.Regularizer] = None,
        kernel_constraint: Optional[constraints.Constraint] = None,
        bias_constraint: Optional[constraints.Constraint] = None,
        recurrent_constraint: Optional[constraints.Constraint] = None,
        **kwargs
    ):
        self_projection = kwargs.pop('self_projection', False)

        if self_projection:
            raise ValueError('`EdgeConv` does not support self projection.')

        super().__init__(**kwargs)

        if units is None:
            raise ValueError('`EdgeConv` requires units (int) to be passed.')

        self.units = units
        self.update_fn = update_fn
        self.update_mode = update_mode.lower()
        if activation is None:
            if self.update_mode.startswith('gru'):
                self.activation = 'tanh'
            else:
                self.activation = 'linear'
        else:
            self.activation = activation
        if use_bias is None:
            use_bias = True if update_mode.startswith('gru') else True
        self.use_bias = use_bias
        self.recurrent_activation = activations.get(recurrent_activation)
        if kernel_initializer is None:
            kernel_initializer = (
                'glorot_uniform' if self.update_mode.startswith('gru')
                else initializers.TruncatedNormal(stddev=0.005)
            )
        self.kernel_initializer  = initializers.get(kernel_initializer)
        self.bias_initializer = initializers.get(bias_initializer or 'zeros')
        self.recurrent_initializer = initializers.get(recurrent_initializer or 'orthogonal')
        self.kernel_regularizer = regularizers.get(kernel_regularizer)
        self.bias_regularizer = regularizers.get(bias_regularizer)
        self.activity_regularizer = regularizers.get(activity_regularizer)
        self.recurrent_regularizer = regularizers.get(recurrent_regularizer)
        self.kernel_constraint = constraints.get(kernel_constraint)
        self.bias_constraint = constraints.get(bias_constraint)
        self.recurrent_constraint = constraints.get(recurrent_constraint)
        self._initialize_edge_state = None
        self._built = False

    def _build(self, initialize_edge_state):

        with tf.init_scope():

            if initialize_edge_state:
                self._initialize_edge_state = True
                self.initial_projection = self._get_dense(self.units)
            else:
                self._initialize_edge_state = False

            if self.update_fn is None:
                self.update_fn = (
                    self._get_gru(self.units) if self.update_mode.startswith('gru')
                    else self._get_dense(self.units)
                )
            elif self.units != self.update_fn.units:
                raise ValueError(
                    'units of `update_fn` needs to match units of this layer.')

            self._built = True

    def call(self, tensor: GraphTensor) -> GraphTensor:

        tensor_orig = tensor
        if isinstance(tensor.node_feature, tf.RaggedTensor):
            tensor = tensor.merge()

        # EdgeConv requires edge features, so if edge features do not exist,
        # we force edge features by initializing them as ones vector
        if tensor.edge_feature is None:
            tensor = tensor.update({
                'edge_feature': tf.ones(
                    shape=[tf.shape(tensor.edge_src)[0], 1], dtype=tf.float32)})

        if not self._built:
            initialize_edge_state = (
                True if not hasattr(tensor, 'edge_state') else False)
            self._build(initialize_edge_state)

        if self._initialize_edge_state:
            edge_state = tf.gather(tensor.node_feature, tensor.edge_src)
            edge_state = tf.concat([edge_state, tensor.edge_feature], axis=-1)
            edge_state = self.initial_projection(edge_state)
            tensor = tensor.update({'edge_state': edge_state})

        edge_state_update = edge_message_step(
            edge_feature=tensor.edge_state,
            edge_src=tensor.edge_src,
            edge_dst=tensor.edge_dst)

        edge_state_update = edge_update_step(
            edge_feature=edge_state_update,
            edge_feature_prev=tensor.edge_state,
            update_projection=self.update_fn)

        return tensor_orig.update({'edge_state': edge_state_update})

    @classmethod
    def from_config(cls: Type['EdgeConv'], config: dict) -> 'EdgeConv':
        initialize_edge_state = config.pop('initialize_edge_state')
        layer = cls(**config)
        if initialize_edge_state is None:
            pass
        else:
            layer._build(initialize_edge_state)
        return layer

    def get_config(self) -> dict:
        base_config = super().get_config()
        config = {
            'units': self.units,
            'update_mode': self.update_mode,
            'update_fn': tf.keras.layers.serialize(self.update_fn),
            'activation':
                activations.serialize(self.activation),
            'recurrent_activation':
                activations.serialize(self.recurrent_activation),
            'use_bias':
                self.use_bias,
            'kernel_initializer':
                initializers.serialize(self.kernel_initializer),
            'bias_initializer':
                initializers.serialize(self.bias_initializer),
            'recurrent_initializer':
                initializers.serialize(self.recurrent_initializer),
            'kernel_regularizer':
                regularizers.serialize(self.kernel_regularizer),
            'bias_regularizer':
                regularizers.serialize(self.bias_regularizer),
            'activity_regularizer':
                regularizers.serialize(self.activity_regularizer),
            'recurrent_regularizer':
                regularizers.serialize(self.recurrent_regularizer),
            'kernel_constraint':
                constraints.serialize(self.kernel_constraint),
            'bias_constraint':
                constraints.serialize(self.bias_constraint),
            'recurrent_constraint':
                constraints.serialize(self.recurrent_constraint),
            'initialize_edge_state': self._initialize_edge_state,
        }
        base_config.update(config)
        return base_config

    def compute_output_shape(
        self,
        input_shape
    ) -> tf.TensorShape:
        return input_shape

    def _get_dense(self, units):
        return keras.layers.Dense(
            units=units,
            activation=self.activation,
            use_bias=self.use_bias,
            kernel_initializer=self.kernel_initializer.from_config(
                self.kernel_initializer.get_config()),
            bias_initializer=self.bias_initializer.from_config(
                self.bias_initializer.get_config()),
            kernel_regularizer=self.kernel_regularizer,
            bias_regularizer=self.bias_regularizer,
            activity_regularizer=self.activity_regularizer,
            kernel_constraint=self.kernel_constraint,
            bias_constraint=self.bias_constraint)

    def _get_gru(self, units):
        return keras.layers.GRUCell(
            units=units,
            activation=self.activation,
            recurrent_activation=self.recurrent_activation,
            use_bias=self.use_bias,
            kernel_initializer=self.kernel_initializer.from_config(
                self.kernel_initializer.get_config()),
            bias_initializer=self.bias_initializer.from_config(
                self.bias_initializer.get_config()),
            recurrent_initializer=self.recurrent_initializer.from_config(
                self.recurrent_initializer.get_config()),
            kernel_regularizer=self.kernel_regularizer,
            bias_regularizer=self.bias_regularizer,
            recurrent_regularizer=self.recurrent_regularizer,
            kernel_constraint=self.kernel_constraint,
            bias_constraint=self.bias_constraint,
            recurrent_constraint=self.recurrent_constraint)

def edge_update_step(
    edge_feature: tf.Tensor,
    edge_feature_prev: tf.Tensor,
    update_projection: Union[
        keras.layers.Dense, keras.layers.GRUCell, keras.layers.LSTMCell],
) -> tf.Tensor:
    if isinstance(update_projection, keras.layers.Dense):
        edge_feature_update = update_projection(
            tf.concat([edge_feature_prev, edge_feature], axis=1))
    else:
        edge_feature_update, _ = update_projection(
            inputs=edge_feature,
            states=edge_feature_prev)
    return edge_feature_update

def edge_message_step(
    edge_feature: tf.Tensor,
    edge_src: tf.Tensor,
    edge_dst: tf.Tensor,
) -> tf.Tensor:
    num_nodes = tf.maximum(tf.reduce_max(edge_src), tf.reduce_max(edge_dst)) + 1
    message = tf.math.unsorted_segment_sum(edge_feature, edge_dst, num_nodes)
    message = tf.gather(message, edge_src)
    message -= _get_reverse_edge_features(edge_feature, edge_src, edge_dst)
    return message

def _get_reverse_edge_features(edge_feature, edge_src, edge_dst):
    edge_exclude = tf.logical_and(
        edge_src[:, None] == edge_dst,
        edge_dst[:, None] == edge_src
    )
    edge_forward, edge_reverse = tf.split(tf.where(edge_exclude), 2, axis=-1)
    return tf.tensor_scatter_nd_add(
        tf.zeros_like(edge_feature),
        tf.expand_dims(edge_forward, -1),
        tf.gather(edge_feature, edge_reverse)
    )


from requests import get
from requests.exceptions import RequestException
from contextlib import closing
from bs4 import BeautifulSoup
import time
import argparse
import re

def simple_get(url):
    """
    Attempts to get the content at `url` by making an HTTP GET request.
    If the content-type of response is some kind of HTML/XML, return the
    text content, otherwise return None.
    """
    time.sleep(.3)
    try:
        with closing(get(url, stream=True)) as resp:
            if is_good_response(resp):
                return resp.content
            else:
                return None

    except RequestException as e:
        log_error('Error during requests to {0} : {1}'.format(url, str(e)))
        return None

def is_good_response(resp):
    """
    Returns True if the response seems to be HTML, False otherwise.
    """
    content_type = resp.headers['Content-Type'].lower()
    return (resp.status_code == 200
            and content_type is not None
            and content_type.find('html') > -1)

def log_error(e):
    """
    It is always a good idea to log errors.
    This function just prints them, but you can
    make it do anything.
    """
    print(e)

parser = argparse.ArgumentParser(description='Download Latin names from the web.')
parser.add_argument('output', help='the output file')

args = parser.parse_args()
# print(args.output)

f = open(args.output, "w")
conversion = {}

for code in range(ord('a'), ord('z') + 1):
    letter = chr(code)
    if letter in ("x", "y"):
        continue
    print("[INFO] Letter " + letter)
    addr = 'http://rbms.info/lpn/' + letter + '/'
    # print(addr)
    raw_html = simple_get(addr)
    html = BeautifulSoup(raw_html, 'html.parser')
    for a in html.select(".entry-content td a"):
        try:
            link = a['href']
            if "#" in link:
                continue
            if not "http://" in link:
                continue
            # print(link)
            name_td = ""
            try:
                name_td = a.parent.parent.select("td:nth-of-type(2)")[0]
            except IndexError:
                continue
            name_links = name_td.select("a")
            modern_name = a.parent.parent.select("td:nth-of-type(2)")[0].getText()
            if len(name_links) > 0:
                modern_name = name_links[0].getText()
            modern_name = modern_name.replace("\n", " ");
            modern_name = re.sub("\\[.*\\]", "", modern_name)

            # print(modern_name)

            if not modern_name:
                print("[ERR] Missing vernacular name for " + link)
                continue

            page_raw_html = simple_get(link)
            page_html = BeautifulSoup(page_raw_html, 'html.parser')
            for p in page_html.select(".entry-content p"):
                text = p.text
                text = text.replace("\n", " ")
                m = re.search("\\((.*?)=", text)
                if m is None:
                    continue
                parts = m.group(1).split(",")
                parts = [part.strip() for part in parts]
                for part in parts:
                    m = re.search("[a-zA-Z]", part)
                    if m is None:
                        print("[ERR] No letters in " + part)
                        continue
                    part = re.sub("\\[.*\\]", "", part)

                    f.write(part)
                    v_parts = modern_name.split("=")
                    for v_part in v_parts:
                        v_part = v_part.strip()
                        f.write("\t")
                        f.write(v_part)
                    f.write("\n")
                # print(parts)
        except KeyError:
            pass
    # for td in html.select(".entry-content tr td:first-child"):
    #     text = str(td)
    #     text = text.replace(">see also", ">")
    #     text = text.replace("> see also", ">")
    #     text = text.replace(">see ", ">")
    #     text = text.replace("> see ", ">")
    #     text = text.replace("*", "")
    #     parts = text.split("<br/>")
    #     for part in parts:
    #         clean = BeautifulSoup(part, "lxml").text.strip()
    #         f.write(clean)
    #         f.write("\n")

f.close()


from django.shortcuts import render, redirect, get_object_or_404
from django.contrib import messages
from django.http import JsonResponse
from datetime import datetime
from django.contrib.auth.decorators import login_required
from custadv.models import Custadv, Custqtn
from custadv.forms import CustadvForm, CustqtnForm

@login_required
def custadvList(request, template_name='custadv/custadvList.html'):
    custadvs = Custadv.objects.all()
    ctx = {}
    ctx['custadvs'] = custadvs
    ctx['title'] = 'List'
    return render(request, template_name, ctx)

@login_required
def custadvCreate(request, template_name='custadv/custadvForm.html'):
    form = CustadvForm(request.POST or None)
    if form.is_valid():
        new_cust = form.save(commit=False)
        new_cust.created_by = request.user
        new_cust.last_updated_by = request.user
        new_cust.save()
        messages.success(request, '資料已新增！')
        return redirect('custadv:custadvUpdate', new_cust.id)
    ctx = {}
    ctx['form'] = form
    ctx['title'] = 'New'
    return render(request, template_name, ctx)

@login_required
def custadvView(request, pk, template_name='custadv/custadvView.html'):
    custadv = get_object_or_404(Custadv, pk=pk)
    ctx = {}
    ctx['custadv'] = custadv
    ctx['custqtns'] = Custqtn.objects.filter(custadv=custadv)
    ctx['title'] = 'View'
    return render(request, template_name, ctx)

@login_required
def custadvUpdate(request, pk, template_name='custadv/custadvForm.html'):
    custadv = get_object_or_404(Custadv, pk=pk)
    form = CustadvForm(request.POST or None, instance=custadv)
    if form.is_valid():
        custadv_obj = form.save(commit=False)
        custadv_obj.last_updated_by = request.user
        custadv_obj.save()
        messages.success(request, '資料已更新！')
        return redirect('custadv:custadvUpdate', custadv.id)
    ctx = {}
    ctx['custadv'] = custadv
    ctx['custqtns'] = Custqtn.objects.filter(custadv=custadv)
    ctx['form'] = form
    ctx['title'] = 'Edit'
    return render(request, template_name, ctx)

@login_required
def custadvDelete(request, pk, template_name='custadv/custadvDelete.html'):
    custadv = get_object_or_404(Custadv, pk=pk)
    if request.method == 'POST':
        custadv.delete()
        messages.success(request, '資料已刪除！')
        return redirect('custadv:custadvList')
    ctx = {}
    ctx['custadv'] = custadv
    ctx['custqtns'] = Custqtn.objects.filter(custadv=custadv)
    ctx['title'] = 'Delete'
    return render(request, template_name, ctx)

@login_required
def custadvData(request, pk):
    custadv = get_object_or_404(Custadv, pk=pk)
    ctx = {}
    if request.POST.get('requestData') == 'master':
        ctx['custadv'] = custadv
        template_name = 'custadv/custMaster.html'
    else:
        ctx['custqtns'] = Custqtn.objects.filter(custadv=custadv)
        template_name = 'custadv/custDetail.html'
    return render(request, template_name, ctx)

@login_required
def custqtnNew(request, parent_pk, template_name='custadv/custqtnNew.html'):
    custadv = get_object_or_404(Custadv, pk=parent_pk)
    form = CustqtnForm(request.POST or None)
    if form.is_valid():
        custqtn = form.save(commit=False)
        custqtn.custadv = custadv
        custqtn.created_by = request.user
        custqtn.last_updated_by = request.user
        custqtn.save()
        messages.success(request, '明細資料已新增！')
        return redirect('custadv:custadvUpdate', parent_pk)
    ctx = {}
    ctx["form"] = form
    ctx["custadv"] = custadv
    ctx['custqtns'] = Custqtn.objects.filter(custadv=custadv)
    ctx['title'] = ' New Detail'
    return render(request, template_name, ctx)

@login_required
def custqtnTabledit(request):
    if request.method == 'POST':
        custqtn = get_object_or_404(Custqtn, pk=request.POST.get('id'))
        if request.POST.get('action') == 'edit':
            custqtn.org = request.POST.get('org')
            custqtn.qtndd = datetime.strptime(request.POST.get('qtndd'),"%Y-%m-%d").date()
            custqtn.qtns = request.POST.get('qtns')
            custqtn.last_updated_by = request.user
            custqtn.save()
            messages.success(request, '明細資料已更新！')
        if request.POST.get('action') == 'delete':
            custqtn.delete()
            messages.success(request, '明細資料已刪除！')

        django_messages = []
        for message in messages.get_messages(request):
            django_messages.append({
                "level": message.level,
                "message": message.message,
                "tags": message.tags,
            })
        ctx = {}
        ctx['request'] = request.POST
        ctx['messages'] = django_messages

        return JsonResponse(ctx)
        # return JsonResponse(request.POST)
    else:
        return render(request, 'main/main.html')


#!/usr/bin/python3

"""Simple ELF Helper for Little-Endian Architectures

basic parsing and assembling of ELF executable linked with:
`-static -Wl,-z,norelro`

More work is needed ro make this compatible with
 * shared/dynamic code
 * situations where  text and data segment live in different memory
   regions - currently only the (text) start address can chosen.

References:
https://linuxhint.com/understanding_elf_file_format/
https://github.com/torvalds/linux/blob/master/include/uapi/linux/elf.h

Type mappings
u32	Elf32_Addr
u16	Elf32_Half
u32	Elf32_Off
s32	Elf32_Sword
u32	Elf32_Word

u64	Elf64_Addr
u16	Elf64_Half
s16	Elf64_SHalf
u64	Elf64_Off
s32	Elf64_Sword
u32	Elf64_Word
u64	Elf64_Xword
s64	Elf64_Sxword

Python struct
u8  -> B
s8  -> b
u16 -> H  ("half word")
s16 -> h
u32 -> I
s32 -> i
u64 -> Q ("quad word")
s64 -> q

"""

import dataclasses
import io
import struct
from typing import List, Dict, Optional, Set, Tuple, Any

from Elf.enum_tab import E_MACHINE, EI_CLASS, EI_DATA, E_TYPE, E_FLAGS_ARM, ST_INFO_BIND, \
    ST_INFO_TYPE
from Elf.enum_tab import P_TYPE, P_FLAGS
from Elf.enum_tab import SH_TYPE, SH_FLAGS

def Align(x, align):
    if align <= 1:
        return x
    return (x + align - 1) // align * align

def Pad(data: bytearray, alignment: int, padding_or_padder: Any):
    if alignment <= 0:
        return
    old_len = len(data)
    new_len = Align(old_len, alignment)
    delta = new_len - old_len
    if isinstance(padding_or_padder, bytes):
        assert old_len % len(padding_or_padder) == 0
        assert alignment % len(padding_or_padder) == 0
        data += padding_or_padder * (delta // len(padding_or_padder))
    else:
        data += padding_or_padder(delta)
    assert len(data) == new_len

def _FirstCharFlags(x: int, flag_map: Dict[int, str]):
    out = []
    while x:
        y = x & (x - 1)
        bit = x - y
        x = y
        out.append(flag_map[bit][0])
    return "".join(out)

EHDR_MAGIC = b'\x7fELF'
TO_BE_FILLED_IN_LATER = -1

@dataclasses.dataclass
class EHdrIdent:
    ei_magic: bytes = EHDR_MAGIC
    ei_class: int = 0
    ei_data: int = 0
    ei_version: int = 0
    ei_osabi: int = 0
    ei_abiversion: int = 0

    # https://docs.python.org/2/library/struct.html
    FORMAT = "<4sbbbbb7s"
    SIZE = struct.calcsize(FORMAT)

    def InitX64(self):
        self.ei_class = EI_CLASS.X_64.value
        self.ei_data = EI_DATA.LSB2.value
        self.ei_version = 1
        self.ei_osabi = 0
        self.ei_abiversion = 0

    def InitA64(self):
        self.ei_class = EI_CLASS.X_64.value
        self.ei_data = EI_DATA.LSB2.value
        self.ei_version = 1
        self.ei_osabi = 0
        self.ei_abiversion = 0

    def InitA32(self):
        self.ei_class = EI_CLASS.X_32.value
        self.ei_data = EI_DATA.LSB2.value
        self.ei_version = 1
        self.ei_osabi = 0
        self.ei_abiversion = 0

    def unpack(self, data: bytes):
        assert EHdrIdent.SIZE == 16
        (self.ei_magic,
         self.ei_class,
         self.ei_data,
         self.ei_version,
         self.ei_osabi,
         self.ei_abiversion,
         _) = struct.unpack(EHdrIdent.FORMAT,
                            data)

    def pack(self):
        assert EHdrIdent.SIZE == 16
        return struct.pack(
            EHdrIdent.FORMAT,
            self.ei_magic,
            self.ei_class,
            self.ei_data,
            self.ei_version,
            self.ei_osabi,
            self.ei_abiversion,
            b"")

    def is_valid(self):
        return (self.ei_magic == EHDR_MAGIC and
                self.ei_class in {EI_CLASS.X_32, EI_CLASS.X_64} and
                self.ei_data in {EI_DATA.LSB2, EI_DATA.MSB2})

@dataclasses.dataclass
class EHdr:
    e_type: int = 0
    e_machine: int = 0
    e_version: int = 0
    e_entry: int = TO_BE_FILLED_IN_LATER
    e_phoff: int = TO_BE_FILLED_IN_LATER
    e_shoff: int = TO_BE_FILLED_IN_LATER
    e_flags: int = 0
    e_ehsize: int = 0
    e_phentsize: int = 0
    e_phnum: int = TO_BE_FILLED_IN_LATER
    e_shentsize: int = 0
    e_shnum: int = TO_BE_FILLED_IN_LATER
    e_shstrndx: int = TO_BE_FILLED_IN_LATER
    FORMAT = {EI_CLASS.X_32: "<2H5I6H", EI_CLASS.X_64: "<2HI3QI6H"}
    SIZE = {k: struct.calcsize(v) for k, v in FORMAT.items()}

    def InitX64Exec(self, shnum, phnum, shstrndx):
        self.e_type = E_TYPE.EXEC.value
        self.e_machine = E_MACHINE.X86_64.value
        self.e_version = 1
        self.e_ehsize = EHdrIdent.SIZE + EHdr.SIZE[EI_CLASS.X_64]
        self.e_phentsize = Segment.SIZE[EI_CLASS.X_64]
        self.e_shentsize = Section.SIZE[EI_CLASS.X_64]
        self.e_shnum = shnum
        self.e_phnum = phnum
        self.e_shstrndx = shstrndx

    def InitA64Exec(self, shnum, phnum, shstrndx):
        self.e_type = E_TYPE.EXEC.value
        self.e_machine = E_MACHINE.AARCH64.value
        self.e_version = 1
        # should we use HARD?
        self.e_flags = 0
        self.e_ehsize = EHdrIdent.SIZE + EHdr.SIZE[EI_CLASS.X_64]
        self.e_phentsize = Segment.SIZE[EI_CLASS.X_64]
        self.e_shentsize = Section.SIZE[EI_CLASS.X_64]
        self.e_shnum = shnum
        self.e_phnum = phnum
        self.e_shstrndx = shstrndx

    def InitA32Exec(self, shnum, phnum, shstrndx):
        self.e_type = E_TYPE.EXEC.value
        self.e_machine = E_MACHINE.ARM.value
        self.e_version = 1
        # should we use HARD?
        self.e_flags = (E_FLAGS_ARM.EABI_VER1 | E_FLAGS_ARM.EABI_VER4).value
        self.e_ehsize = EHdrIdent.SIZE + EHdr.SIZE[EI_CLASS.X_32]
        self.e_phentsize = Segment.SIZE[EI_CLASS.X_32]
        self.e_shentsize = Section.SIZE[EI_CLASS.X_32]
        self.e_shnum = shnum
        self.e_phnum = phnum
        self.e_shstrndx = shstrndx

    def unpack(self, which, data: bytes):
        fmt = EHdr.FORMAT[which]
        (self.e_type, self.e_machine, self.e_version, self.e_entry, self.e_phoff,
         self.e_shoff, self.e_flags, self.e_ehsize, self.e_phentsize, self.e_phnum,
         self.e_shentsize, self.e_shnum, self.e_shstrndx) = struct.unpack(fmt, data)

    def pack(self, which):
        fmt = EHdr.FORMAT[which]
        return struct.pack(
            fmt,
            self.e_type,
            self.e_machine,
            self.e_version,
            self.e_entry,
            self.e_phoff,
            self.e_shoff,
            self.e_flags,
            self.e_ehsize,
            self.e_phentsize,
            self.e_phnum,
            self.e_shentsize,
            self.e_shnum,
            self.e_shstrndx)

# More info in ELF for the ARM Architecture ABI r2.10
# https://developer.arm.com/documentation/ihi0044/f/
ARM_ATTRIBUTES = bytes([0x41, 0x11, 0, 0,
                        0, 0x61, 0x65, 0x61,
                        0x62, 0x69, 0, 1,
                        7, 0, 0, 0,
                        8, 1])

@dataclasses.dataclass
class Section:
    """An Elf Section (glorified Shdr)"""
    sh_name: int = TO_BE_FILLED_IN_LATER
    sh_type: int = 0
    sh_flags: int = 0
    sh_addr: int = TO_BE_FILLED_IN_LATER
    sh_offset: int = TO_BE_FILLED_IN_LATER
    sh_size: int = 0
    sh_link: int = 0
    sh_info: int = 0
    sh_addralign: int = 0
    sh_entsize: int = 0
    # non-elf extras
    name: str = ""  # maybe switch to bytes and propagate to Section in CodeGenXXX
    data: bytearray = dataclasses.field(default_factory=bytearray)
    index: int = TO_BE_FILLED_IN_LATER

    FORMAT = {EI_CLASS.X_32: "10I", EI_CLASS.X_64: "2I4Q2I2Q"}
    SIZE = {k: struct.calcsize(v) for k, v in FORMAT.items()}

    def PadData(self, n: int, padding_or_padder: Any):
        if self.sh_addralign < n:
            self.sh_addralign = n
        Pad(self.data, n, padding_or_padder)
        self.sh_size = len(self.data)

    def AddData(self, data: bytes):
        self.data += data
        self.sh_size = len(self.data)

    def SetData(self, data: bytes):
        self.data = data
        self.sh_size = len(self.data)

    @classmethod
    def MakeSection(cls, name: str, alignment: int, kind: SH_TYPE, flags: SH_FLAGS) -> "Section":
        self = Section()
        self.name = name
        self.sh_flags = flags.value
        self.sh_type = kind.value
        self.sh_addralign = alignment
        return self

    @classmethod
    def MakeSectionNull(cls) -> "Section":
        return Section.MakeSection("", 0, SH_TYPE.X_NULL, SH_FLAGS(0))

    @classmethod
    def MakeSectionText(cls, alignment: int) -> "Section":
        return Section.MakeSection(
            ".text", alignment, SH_TYPE.PROGBITS, SH_FLAGS.ALLOC | SH_FLAGS.EXECINSTR)

    @classmethod
    def MakeSectionRodata(cls, alignment: int) -> "Section":
        return Section.MakeSection(".rodata", alignment, SH_TYPE.PROGBITS, SH_FLAGS.ALLOC)

    @classmethod
    def MakeSectionData(cls, alignment: int) -> "Section":
        return Section.MakeSection(
            ".data", alignment, SH_TYPE.PROGBITS, SH_FLAGS.ALLOC | SH_FLAGS.WRITE)

    @classmethod
    def MakeSectionBss(cls, alignment: int) -> "Section":
        return Section.MakeSection(
            ".bss", alignment, SH_TYPE.NOBITS, SH_FLAGS.ALLOC | SH_FLAGS.WRITE)

    @classmethod
    def MakeSectionStrTab(cls, name: str) -> "Section":
        return Section.MakeSection(name, 1, SH_TYPE.STRTAB, SH_FLAGS(0))

    @classmethod
    def MakeSectionSymTab(cls, name: str, which, strtab_ndx: int) -> "Section":
        # TODO: is the alignment different for 64bit?
        sec = Section.MakeSection(name, 4, SH_TYPE.SYMTAB, SH_FLAGS(0))
        sec.sh_link = strtab_ndx
        sec.sh_info = TO_BE_FILLED_IN_LATER
        sec.sh_entsize = Symbol.SIZE[which]
        return sec

    @classmethod
    def MakeSectionArmAttributes(cls) -> "Section":
        return Section.MakeSection(".ARM.attributes", 1, SH_TYPE.ARM_ATTRIBUTES, SH_FLAGS(0))

    def unpack(self, which, data: bytes):
        fmt = Section.FORMAT[which]
        (self.sh_name,
         self.sh_type,
         self.sh_flags,
         self.sh_addr,
         self.sh_offset,
         self.sh_size,
         self.sh_link,
         self.sh_info,
         self.sh_addralign,
         self.sh_entsize) = struct.unpack(fmt,
                                          data)

    def pack(self, which):
        fmt = Section.FORMAT[which]
        return struct.pack(
            fmt,
            self.sh_name,
            self.sh_type,
            self.sh_flags,
            self.sh_addr,
            self.sh_offset,
            self.sh_size,
            self.sh_link,
            self.sh_info,
            self.sh_addralign,
            self.sh_entsize)

    def verify_vaddr_and_offset(self, vaddr, offset) -> Tuple[int, int]:
        """Checks sh_addr and sh_offset."""
        if self.sh_type == SH_TYPE.X_NULL:  # usually NULL
            assert self.sh_addr == 0
            assert self.sh_offset == 0
            return vaddr, offset
        if self.sh_type == SH_TYPE.NOBITS:
            if self.name != ".tbss":
                # we have other ways to get that space via the TLS segments
                vaddr = Align(vaddr, self.sh_addralign)
                assert self.sh_offset == offset
                assert self.sh_addr == vaddr
                vaddr += self.sh_size
            return vaddr, offset

        offset = Align(offset, self.sh_addralign)
        vaddr = Align(vaddr, self.sh_addralign)
        assert self.sh_offset == offset, f"bad offset {offset:x} vs {self.sh_offset:x}"
        if SH_FLAGS(self.sh_flags) & SH_FLAGS.ALLOC:
            assert self.sh_addr == vaddr, f"bad addr {vaddr:x} vs {self.sh_addr:x}"
        else:
            assert self.sh_addr == 0
        return vaddr + self.sh_size, offset + self.sh_size

    def update_vaddr_and_offset(self, vaddr, offset) -> Tuple[int, int]:
        """Fills in sh_addr and sh_offset."""
        if self.sh_type == SH_TYPE.X_NULL:  # usually NULL
            self.sh_addr = 0
            self.sh_offset = 0
            return vaddr, offset
        if self.sh_type == SH_TYPE.NOBITS:
            if self.name != ".tbss":
                # we have other ways to get that space via the TLS segments
                vaddr = Align(vaddr, self.sh_addralign)
                self.sh_offset = offset
                self.sh_addr = vaddr
                vaddr += self.sh_size
            return vaddr, offset

        assert self.sh_size == len(self.data), f"{self}"
        offset = Align(offset, self.sh_addralign)
        vaddr = Align(vaddr, self.sh_addralign)
        self.sh_offset = offset
        if SH_FLAGS(self.sh_flags) & SH_FLAGS.ALLOC:
            self.sh_addr = vaddr
        else:
            self.sh_addr = 0
        return vaddr + self.sh_size, offset + self.sh_size

    def type_str(self) -> str:
        return SH_TYPE(self.sh_type).name

    def flag_str(self):
        sh_flags = SH_FLAGS(self.sh_flags)
        flags = [f for f in SH_FLAGS if f & sh_flags]
        return "".join([f.name[0] for f in flags])

    def __str__(self):
        return (
            f"{self.name:20s} {self.type_str():12s} "
            f"{self.flag_str():5} {self.sh_addr:7x} "
            f"{self.sh_offset:7x} {self.sh_size:7x} {self.sh_link:2x} {self.sh_info:4x} "
            f"{self.sh_addralign:3x} {self.sh_entsize:2x}")

@dataclasses.dataclass
class Segment:
    """An Elf Segment (glorified phdr) - also includes Elf Sections"""
    p_type: int = TO_BE_FILLED_IN_LATER
    p_offset: int = TO_BE_FILLED_IN_LATER
    p_vaddr: int = TO_BE_FILLED_IN_LATER
    p_paddr: int = TO_BE_FILLED_IN_LATER
    p_filesz: int = TO_BE_FILLED_IN_LATER
    p_memsz: int = TO_BE_FILLED_IN_LATER
    p_flags: int = TO_BE_FILLED_IN_LATER
    p_align: int = TO_BE_FILLED_IN_LATER
    # non-elf extras
    is_pseudo: bool = False
    is_auxiliary: bool = False
    hard_align: bool = False
    sections: List[Section] = dataclasses.field(default_factory=list)

    FORMAT = {EI_CLASS.X_32: "<8I", EI_CLASS.X_64: "<2I6Q"}
    SIZE = {k: struct.calcsize(v) for k, v in FORMAT.items()}

    @classmethod
    def MakePseudoSegment(cls):
        self = Segment()
        self.is_pseudo = True
        self.p_type = P_TYPE.X_NULL.value
        self.p_align = 0
        self.p_flags = 0
        return self

    @classmethod
    def MakeExeSegment(cls, align: int):
        self = Segment()
        self.p_type = P_TYPE.LOAD
        self.p_flags = (P_FLAGS.R | P_FLAGS.X).value
        self.p_align = align
        return self

    @classmethod
    def MakeRWSegment(cls, align: int):
        self = Segment()
        self.p_type = P_TYPE.LOAD.value
        self.p_flags = (P_FLAGS.R | P_FLAGS.W).value
        self.p_align = align
        return self

    @classmethod
    def MakeROSegment(cls, align: int):
        self = Segment()
        self.p_type = P_TYPE.LOAD.value
        self.p_flags = P_FLAGS.R.value
        self.p_align = align
        return self

    def unpack(self, which, data: bytes):
        fmt = Segment.FORMAT[which]
        if which == EI_CLASS.X_32:
            (self.p_type,
             self.p_offset,
             self.p_vaddr,
             self.p_paddr,
             self.p_filesz,
             self.p_memsz,
             self.p_flags,
             self.p_align) = struct.unpack(fmt,
                                           data)
        else:
            assert which == EI_CLASS.X_64
            (self.p_type, self.p_flags, self.p_offset, self.p_vaddr, self.p_paddr,
             self.p_filesz, self.p_memsz, self.p_align) = struct.unpack(fmt, data)

    def pack(self, which):
        fmt = Segment.FORMAT[which]
        if which == EI_CLASS.X_32:
            return struct.pack(
                fmt,
                self.p_type,
                self.p_offset,
                self.p_vaddr,
                self.p_paddr,
                self.p_filesz,
                self.p_memsz,
                self.p_flags,
                self.p_align)
        else:
            assert which == EI_CLASS.X_64
            return struct.pack(
                fmt,
                self.p_type,
                self.p_flags,
                self.p_offset,
                self.p_vaddr,
                self.p_paddr,
                self.p_filesz,
                self.p_memsz,
                self.p_align)

    def flag_str(self):
        p_flags = P_FLAGS(self.p_flags)
        flags = [f for f in P_FLAGS if f & p_flags]
        return "".join([f.name[0] for f in flags])

    def type_str(self):
        return P_TYPE(self.p_type).name

    # sadly. this is NOT unique, e.g. there might be multiple (LOAD, "R") phdrs
    def flavor(self):
        return self.p_type, self.flag_str()

    def contains_vaddr(self, vaddr: int):
        assert self.p_type == P_TYPE.LOAD
        return self.p_vaddr <= vaddr < self.p_vaddr + self.p_memsz

    def __str__(self):
        return (
            f"{self.type_str():12} {self.flag_str():3} "
            f"{self.p_offset:7x} {self.p_vaddr:7x} {self.p_paddr:7x} {self.p_filesz:7x} "
            f"{self.p_memsz:7x} {self.p_align:4x}")

    def __repr__(self):
        return str(self)

@dataclasses.dataclass
class Symbol:
    """An Elf Symbol"""
    st_name: int = 0
    st_value: int = 0
    st_size: int = 0
    st_bind: int = 0
    st_type: int = 0
    st_other: int = 0
    st_shndx: int = 0
    # non-elf extras
    name: str = ""
    section: Optional[Section] = None

    FORMAT = {EI_CLASS.X_32: "3IBBH", EI_CLASS.X_64: "IBBHQQ"}
    SIZE = {k: struct.calcsize(v) for k, v in FORMAT.items()}

    @classmethod
    def Init(cls, name, is_local, sec=None, value=0) -> "Symbol":
        self = Symbol()
        self.name = name
        self.st_bind = ST_INFO_BIND.LOCAL if is_local else ST_INFO_BIND.GLOBAL
        self.st_value = value
        self.st_type = ST_INFO_TYPE.NOTYPE
        self.section = sec
        return self

    def is_undefined(self):
        return self.section is None

    def unpack(self, which, data: bytes):
        fmt = Symbol.FORMAT[which]
        if which == EI_CLASS.X_32:
            (self.st_name,
             self.st_value,
             self.st_size,
             st_info,
             self.st_other,
             self.st_shndx) = struct.unpack(fmt, data)
        else:
            assert which == EI_CLASS.X_64
            (self.st_name, st_info, self.st_other, self.st_shndx,
             self.st_value, self.st_size) = struct.unpack(fmt, data)
        self.st_bind = st_info >> 4
        self.st_type = st_info & 0xf

    def pack(self, which):
        st_info = (self.st_bind << 4) | self.st_type
        fmt = Symbol.FORMAT[which]
        assert self.st_value != -1, f"undefined sym {self.name}"
        if which == EI_CLASS.X_32:
            return struct.pack(
                fmt, self.st_name,
                self.st_value,
                self.st_size,
                st_info,
                self.st_other,
                self.st_shndx
            )
        else:
            assert which == EI_CLASS.X_64
            return struct.pack(
                fmt, self.st_name, st_info, self.st_other, self.st_shndx,
                self.st_value, self.st_size)

    def __str__(self):
        return (
            f"{self.name:20s} {self.st_value:7x} {self.st_shndx} "
            f"{ST_INFO_BIND(self.st_bind).name:10}"
            f"{ST_INFO_TYPE(self.st_type).name:10}")

@dataclasses.dataclass
class Reloc:
    """An Elf Relocation with addend"""
    r_offset: int = 0
    r_type: int = 0
    r_sym: int = 0
    r_addend: int = 0
    # non-elf extras
    symbol: Symbol = None
    section: Optional[Section] = None

    FORMAT = {EI_CLASS.X_32: "IIi", EI_CLASS.X_64: "QIIq"}
    SIZE = {k: struct.calcsize(v) for k, v in FORMAT.items()}

    @classmethod
    def Init(cls, kind: int, sec: Section, offset: int, sym: Symbol, addend: int) -> "Reloc":
        out = Reloc()
        out.r_offset = offset
        out.section = sec
        out.r_type = kind
        out.r_addend = addend
        out.symbol = sym
        return out

    def unpack(self, which, data: bytes):
        if which == EI_CLASS.X_64:
            self.r_offset, self.r_type, self.r_sym, self.r_addend = struct.unpack(
                Reloc.FORMAT[which], data)
        else:
            self.r_offset, r_info, self.r_addend = struct.unpack(Reloc.FORMAT[which], data)
            self.r_type = r_info & 0xff
            self.r_sym = r_info >> 8

    def pack(self, which):
        if which == EI_CLASS.X_64:
            return struct.pack(Reloc.FORMAT[which], self.r_offset, self.r_type, self.r_sym,
                               self.r_addend)
        else:
            return struct.pack(Reloc.FORMAT[which], self.r_offset,
                               self.r_type | (self.r_sym << 8, self.r_addend))

    def __str__(self):
        return f"{self.section.name}  {self.r_offset} {self.r_type} {self.symbol.name}"

# These Segment types are overlapping with LOAD Segments and the Sections they
# contain a are duplicates of those in the LOAD Segment.
_AUXILIARY_PHDR_TYPE: Set[int] = {
    P_TYPE.GNU_STACK,
    P_TYPE.GNU_PROPERTY,
    P_TYPE.GNU_RELRO,
    P_TYPE.GNU_EH_FRAME,
    P_TYPE.DYNAMIC,
    P_TYPE.TLS,
    P_TYPE.NOTE,
    P_TYPE.INTERP,
}

def is_inside(src, dst):
    return (dst[0] <= src[0] <= dst[0] + dst[1] and
            dst[0] <= src[0] + src[1] <= dst[0] + dst[1])

def str_offset_to_name(offset, sec_symtab: Section):
    data = sec_symtab.data
    name = bytearray()
    while data[offset] != 0:
        name.append(data[offset])
        offset += 1
    return name.decode("utf-8")

def MakeSecStrTabContents(sections: List[Section]):
    out = bytearray()
    out += b"\0"
    for sec in sections:
        if sec.name:
            sec.sh_name = len(out)
            out += bytes(sec.name, "utf-8") + b"\0"
        else:
            sec.sh_name = 0
    return out

class Executable:
    """An ELF Executable"""

    def __init__(self):
        self.ehdr_ident = EHdrIdent()
        self.ehdr = EHdr()
        self.sections: List[Section] = []
        self.segments: List[Segment] = []
        self.start_vaddr = 0
        self.symbols: List[Symbol] = []

    def InitWithSectionsAndSegments(self, start_vaddr: int,
                                    sections: List[Section],
                                    segments: List[Segment]):
        assert not self.sections and not self.segments
        assert sections[-1].sh_type == SH_TYPE.STRTAB.value
        assert sections[-1].name == ".shstrtab"

        for n, sec in enumerate(sections):
            sec.index = n

        self.start_vaddr = start_vaddr
        self.sections = sections
        self.segments = segments

    @classmethod
    def MakeExecutableA32(cls, start_vaddr: int,
                          sections: List[Section],
                          segments: List[Segment]):
        exe = Executable()
        exe.ehdr_ident.InitA32()
        exe.ehdr.InitA32Exec(len(sections),
                             len([p for p in segments
                                  if not p.is_pseudo]),
                             len(sections) - 1)
        exe.InitWithSectionsAndSegments(start_vaddr, sections, segments)
        return exe

    @classmethod
    def MakeExecutableA64(cls, start_vaddr: int,
                          sections: List[Section],
                          segments: List[Segment]):
        exe = Executable()
        exe.ehdr_ident.InitA64()
        exe.ehdr.InitA64Exec(len(sections),
                             len([p for p in segments
                                  if not p.is_pseudo]),
                             len(sections) - 1)
        exe.InitWithSectionsAndSegments(start_vaddr, sections, segments)
        return exe

    @classmethod
    def MakeExecutableX64(cls, start_vaddr: int,
                          sections: List[Section],
                          segments: List[Segment]):
        exe = Executable()
        exe.ehdr_ident.InitX64()
        exe.ehdr.InitX64Exec(len(sections),
                             len([p for p in segments
                                  if not p.is_pseudo]),
                             len(sections) - 1)
        exe.InitWithSectionsAndSegments(start_vaddr, sections, segments)
        return exe

    def phdrs_containing_shdr(self, shdr: Section):
        filesz = 0 if shdr.sh_type == SH_TYPE.NOBITS else shdr.sh_size
        out = []
        for phdr in self.segments:
            # check containment in file
            if not is_inside((shdr.sh_offset, filesz),
                             (phdr.p_offset, phdr.p_filesz)):
                continue
            # check containment in address space
            if (shdr.sh_addr != 0 and
                    not is_inside((shdr.sh_addr, shdr.sh_size),
                                  (phdr.p_vaddr, phdr.p_memsz))):
                continue
            out.append(phdr)
        return out

    def save(self, stream: io.BytesIO):
        """ Save"""
        which = self.ehdr_ident.ei_class

        offset = 0
        data = self.ehdr_ident.pack()
        offset += len(data)
        stream.write(data)
        data = self.ehdr.pack(which)
        offset += len(data)
        stream.write(data)
        assert offset == self.ehdr.e_phoff
        for phdr in self.segments:
            if phdr.is_pseudo:
                continue
            data = phdr.pack(which)
            offset += len(data)
            stream.write(data)

        # Note pseudo segment will be last
        for phdr in self.segments:
            if phdr.is_auxiliary:
                continue
            for shdr in phdr.sections:
                if False:
                    print(
                        f"writing {shdr.name:20s} offset is {offset:5x} "
                        f"want{shdr.sh_offset:5x} "
                        f"align {shdr.sh_addralign:5x} "
                        f"size {shdr.sh_size:5x}")
                if shdr.sh_size == 0 or shdr.sh_type == SH_TYPE.NOBITS:
                    continue

                new_offset = shdr.sh_offset
                if new_offset != offset:
                    assert new_offset > offset, f"offset corruption"
                    padding = (new_offset - offset) * b"\0"
                    if False:
                        print(f"adding {len(padding)} byte padding")
                    offset += len(padding)
                    stream.write(padding)

                assert shdr.sh_size == len(
                    shdr.data), f"size mismatch {shdr.sh_size:x} vs {len(shdr.data):x}"
                offset += len(shdr.data)
                stream.write(shdr.data)

        # hack
        new_offset = Align(offset, 16 if which == EI_CLASS.X_64 else 4)
        padding = (new_offset - offset) * b"\0"
        offset += len(padding)
        stream.write(padding)

        assert offset == self.ehdr.e_shoff, f"e_shoff mismatch {offset:x} vs {self.ehdr.e_shoff:x}"
        for phdr in self.segments:
            if phdr.is_auxiliary:
                continue
            for shdr in phdr.sections:
                data = shdr.pack(which)
                offset += len(data)
                stream.write(data)

    def _load_segements(self, fin: io.BytesIO, which) -> Tuple[int, List[Segment]]:
        size = Segment.SIZE[which]
        assert size == self.ehdr.e_phentsize
        fin.seek(self.ehdr.e_phoff)
        start_vaddr = 0
        segments = []
        for i in range(self.ehdr.e_phnum):
            phdr = Segment()
            segments.append(phdr)
            phdr.unpack(which, fin.read(size))
            phdr.hard_align = phdr.p_vaddr % phdr.p_align == 0
            if start_vaddr == 0 and phdr.p_type == P_TYPE.LOAD:
                start_vaddr = phdr.p_vaddr
            phdr.is_auxiliary = (phdr.p_type in _AUXILIARY_PHDR_TYPE)
        return start_vaddr, segments

    def _load_sections(self, fin: io.BytesIO, which) -> List[Section]:
        size = Section.SIZE[which]
        assert size == self.ehdr.e_shentsize
        fin.seek(self.ehdr.e_shoff)
        shdrs: List[Section] = []
        for i in range(self.ehdr.e_shnum):
            shdr = Section()
            shdrs.append(shdr)
            shdr.unpack(which, fin.read(size))
        # retrieve data
        for shdr in shdrs:
            if shdr.sh_type == SH_TYPE.NOBITS:
                continue
            else:
                fin.seek(shdr.sh_offset)
                shdr.data = fin.read(shdr.sh_size)
        # retrieve section names
        sh_strtab = shdrs[self.ehdr.e_shstrndx]
        for shdr in shdrs:
            shdr.name = str_offset_to_name(shdr.sh_name, sh_strtab)
        return shdrs

    def _load_symbols(self, fin: io.BytesIO, which, shdrs):
        symtab = None
        for shdr in shdrs:
            if shdr.sh_type == SH_TYPE.SYMTAB.value and shdr.name == ".symtab":
                assert symtab is None
                symtab = shdr
        if not symtab:
            return
        strtab = shdrs[symtab.sh_link]
        # note, sh_info is the index of the last local symbol + 1
        size = Symbol.SIZE[which]
        n = symtab.sh_size // size
        assert n * size == symtab.sh_size
        fin.seek(symtab.sh_offset)
        for i in range(n):
            sym = Symbol()
            self.symbols.append(sym)
            if len(shdrs) > sym.st_shndx > 0:
                sym.section = shdrs[sym.st_shndx]
            sym.unpack(which, fin.read(size))
            sym.name = str_offset_to_name(sym.st_name, strtab)

    def load(self, fin: io.BytesIO):
        """Initialize the object from the content of a file """
        assert not self.sections and not self.segments
        self.ehdr_ident.unpack(fin.read(EHdrIdent.SIZE))
        assert self.ehdr_ident.is_valid()
        assert self.ehdr_ident.ei_data == EI_DATA.LSB2

        which = self.ehdr_ident.ei_class

        self.ehdr.unpack(which, fin.read(EHdr.SIZE[which]))

        self.start_vaddr, self.segments = self._load_segements(fin, which)
        self.sections = self._load_sections(fin, which)
        self._load_symbols(fin, which, self.sections)

        # assign sections to segments
        # The null sections goes into the first segment
        # `pseudd_segment` holds all the unmapped sections
        pseudo_segment: Optional[Segment] = None
        for sec in self.sections:
            segments = self.phdrs_containing_shdr(sec)
            if segments:
                assert pseudo_segment is None, (
                    f"{sec} - all mapped sections must precede unmapped ones")
                if len(segments) > 1:
                    for phdr in segments:
                        assert phdr.p_type == P_TYPE.LOAD or phdr.is_auxiliary, f"{phdr}"
                for phdr in segments:
                    phdr.sections.append(sec)

            else:
                assert sec.sh_type != SH_TYPE.X_NULL, f"unexpected {sec}"
                if pseudo_segment is None:
                    pseudo_segment = Segment.MakePseudoSegment()
                    self.segments.append(pseudo_segment)
                pseudo_segment.sections.append(sec)

        # print(self)

    def combined_header_size(self) -> int:
        """assumes a layout where the phdrs follow directly after the ehdr"""
        which = self.ehdr_ident.ei_class
        num_phdrs = len([phdr for phdr in self.segments if not phdr.is_pseudo])
        return EHdrIdent.SIZE + EHdr.SIZE[which] + num_phdrs * Segment.SIZE[which]

    def verify_vaddrs_and_offsets(self):
        """Compute the section addresses and offsets.

        Note, this assumes we already know how many segments there are which must be
        reflected in len(self.phdrs)
        """
        which = self.ehdr_ident.ei_class
        offset = 0
        vaddr = 0
        for phdr in self.segments:
            if phdr.is_auxiliary:
                continue
            if offset == 0:
                offset = self.combined_header_size()
                vaddr = self.combined_header_size() + self.start_vaddr
            elif phdr.hard_align:
                vaddr = Align(vaddr + 1, phdr.p_align)
                offset = Align(offset + 1, phdr.p_align)
            else:
                vaddr = vaddr + phdr.p_align
            print("NEW PHDR", phdr)
            for shdr in phdr.sections:
                print("SHDR", shdr)
                vaddr, offset = shdr.verify_vaddr_and_offset(vaddr, offset)

        # the section headers are written last
        offset = Align(offset, 16 if which == EI_CLASS.X_64 else 4)
        assert self.ehdr.e_shoff == offset, f"{offset:x}  {self.ehdr.e_shoff:x}"
        assert self.ehdr.e_phoff == EHdrIdent.SIZE + EHdr.SIZE[which]

        # check phdr
        is_first_load = True
        for phdr in self.segments:
            if phdr.is_pseudo:
                continue

            first_shdr = phdr.sections[0]
            if phdr.p_type == P_TYPE.LOAD and is_first_load:
                assert phdr.p_offset == 0, f"{phdr.p_offset:x}"
                assert phdr.p_vaddr == self.start_vaddr
                is_first_load = False
            else:
                assert phdr.p_offset == first_shdr.sh_offset
                assert phdr.p_vaddr == first_shdr.sh_addr
            assert phdr.p_paddr == phdr.p_vaddr

            last_shdr = phdr.sections[-1]
            filesz = 0 if last_shdr.sh_type == SH_TYPE.NOBITS else last_shdr.sh_size
            assert phdr.p_filesz == last_shdr.sh_offset + filesz - phdr.p_offset
            assert phdr.p_memsz == last_shdr.sh_addr + last_shdr.sh_size - phdr.p_vaddr

    def update_vaddrs_and_offset(self):
        """Compute the section addresses and offsets.

        Note, this assumes we already know how many segments there are which must be
        reflected in len(self.phdrs)
        """
        # print("COMPUTE VADDRS AND OFFSETS")
        which = self.ehdr_ident.ei_class
        # update shdr first
        offset = 0
        vaddr = 0
        for phdr in self.segments:
            if phdr.is_auxiliary:
                continue
            if offset == 0:
                offset = self.combined_header_size()
                vaddr = self.combined_header_size() + self.start_vaddr
            elif phdr.hard_align:
                vaddr = Align(vaddr + 1, phdr.p_align)
                offset = Align(offset + 1, phdr.p_align)
            else:
                vaddr += phdr.p_align
            for sec in phdr.sections:
                assert sec in self.sections
                vaddr, offset = sec.update_vaddr_and_offset(vaddr, offset)

        # update ehdr
        # the section headers are written last
        offset = Align(offset, 16 if which == EI_CLASS.X_64 else 4)
        self.ehdr.e_shoff = offset
        self.ehdr.e_phoff = EHdrIdent.SIZE + EHdr.SIZE[which]

        # now update phdr
        is_first_load = True
        for phdr in self.segments:
            if phdr.is_pseudo:
                continue

            first_shdr = phdr.sections[0]
            if phdr.p_type == P_TYPE.LOAD and is_first_load:
                phdr.p_offset = 0
                phdr.p_vaddr = self.start_vaddr
                is_first_load = False
            else:
                phdr.p_offset = first_shdr.sh_offset
                phdr.p_vaddr = first_shdr.sh_addr
            phdr.p_paddr = phdr.p_vaddr

            last_shdr = phdr.sections[-1]
            filesz = 0 if last_shdr.sh_type == SH_TYPE.NOBITS else last_shdr.sh_size
            phdr.p_filesz = last_shdr.sh_offset + filesz - phdr.p_offset
            phdr.p_memsz = last_shdr.sh_addr + last_shdr.sh_size - phdr.p_vaddr

    def __str__(self):
        out = [
            str(self.ehdr_ident),
            str(self.ehdr),
            "PHDR type      flags   offset    vaddr  paddr  filesz   memsz align"
        ]

        for i, phdr in enumerate(self.segments):
            out.append(f"[{i:2}] {phdr}")

        out.append("SHDR  name                  type      flags     addr  offset    size")

        for i, shdr in enumerate(self.sections):
            out.append(f"[{i:2}] {shdr}")

        # expected_phdrs = SHDR_TO_PHDRS[shdr.name]
        # actual_phdrs = [(p.p_type, p.flag_str()) for p in phdrs]
        # for ep in expected_phdrs:
        #     assert ep in actual_phdrs, f"{shdr.name} {ep} {actual_phdrs}"
        out.append(f"HEADER SIZE: {self.combined_header_size():x}")

        out.append("SYM  name                  type      flags     addr  offset    size")
        for i, sym in enumerate(self.symbols):
            out.append(f"[{i:3}] {sym}")
        return "\n".join(out)

TESTS = [
    "/bin/bash",
    "/usr/bin/gcc",
]

# /usr/lib/ld-linux.so.2
#
# /bin/Xwayland
# /usr/bin/qemu-aarch64_be-static
#  /usr/bin/gcc
#  /usr/bin/clang
#  /opt/google/chrome/chrome

if __name__ == "__main__":
    import sys
    assert len(sys.argv) > 1
    sys.argv.pop(0)
    mode = sys.argv.pop(0)

    def verify(exe: str):
        fin = open(exe, "rb")
        obj = Executable()
        obj.load(fin)
        obj.verify_vaddrs_and_offsets()

    def clone(exe: str, exe_clone: str):
        fin = open(exe, "rb")
        obj = Executable()
        obj.load(fin)
        obj.verify_vaddrs_and_offsets()
        fout = open(exe_clone, "wb")
        obj.save(fout)

    if mode == "verify":
        assert len(sys.argv) == 1
        verify(sys.argv[0])
    elif mode == "clone":
        assert len(sys.argv) == 2
        clone(sys.argv[0], sys.argv[1])
    else:
        assert False, f"unknown mode {mode}"