# Copyright 2021 The Pigweed Authors
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy of
# the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.
"""Script for generating test bundles"""

import argparse
import subprocess
import sys
from typing import Dict

from pw_software_update import tuf_pb2, metadata, update_bundle_pb2

HEADER = """// Copyright 2021 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy
// of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.

#pragma once

#include "pw_bytes/span.h"

"""


def byte_array_declaration(data: bytes, name: str) -> str:
    """Generates a byte C array declaration for a byte array"""
    type_name = '[[maybe_unused]] const std::byte'
    byte_str = ''.join([f'std::byte{{0x{b:02x}}},' for b in data])
    array_body = f'{{{byte_str}}}'
    return f'{type_name} {name}[] = {array_body};'


def proto_array_declaration(proto, name: str) -> str:
    """Generates a byte array declaration for a proto"""
    return byte_array_declaration(proto.SerializeToString(), name)


def sign_metadata(role: str, metadata_proto):
    """Signs a serialized metadata for a given role"""
    signed = getattr(tuf_pb2, f'Signed{role.capitalize()}Metadata')()
    serialized = metadata_proto.SerializeToString()
    setattr(signed, f'serialized_{role}_metadata', serialized)
    # TODO(pwbug:456): Generate signatures.

    return signed


class Bundle:
    """A helper for test UpdateBundle generation"""
    def __init__(self):
        self._payloads: Dict[str, bytes] = {}

    def add_payload(self, name: str, payload: bytes) -> None:
        """Adds a payload to the bundle"""
        self._payloads[name] = payload

    def generate_targets_metadata(self) -> tuf_pb2.TargetsMetadata:
        """Generates the targets metadata"""
        targets = metadata.gen_targets_metadata(self._payloads)
        return targets

    def generate_signed_metadta(self, role: str):
        """Generate a signed metadata of a given role"""
        unsigned = getattr(self, f'generate_{role}_metadata')()
        return sign_metadata(role, unsigned)

    def generate_bundle(self) -> update_bundle_pb2.UpdateBundle:
        """Generate the update bundle"""
        bundle = update_bundle_pb2.UpdateBundle()
        bundle.targets_metadata['targets'].CopyFrom(
            self.generate_signed_metadta('targets'))
        for name, payload in self._payloads.items():
            bundle.target_payloads[name] = payload

        return bundle


def parse_args():
    """Setup argparse."""
    parser = argparse.ArgumentParser()
    parser.add_argument("output_header",
                        help="output path of the generated C header")
    return parser.parse_args()


def main() -> int:
    """Main"""
    args = parse_args()

    test_bundle = Bundle()

    # Add some update files.
    test_bundle.add_payload('file1', 'file 1 content'.encode())  # type: ignore
    test_bundle.add_payload('file2', 'file 2 content'.encode())  # type: ignore

    update_bundle_proto = test_bundle.generate_bundle()

    with open(args.output_header, 'w') as header:
        header.write(HEADER)
        header.write(
            proto_array_declaration(update_bundle_proto, 'kTestBundle'))

    subprocess.run([
        'clang-format',
        '-i',
        args.output_header,
    ], check=True)

    return 0


if __name__ == "__main__":
    sys.exit(main())
