"""
ISO 9660 (cdrom) file system parser.

Documents:
- Standard ECMA-119 (december 1987)
  http://www.nondot.org/sabre/os/files/FileSystems/iso9660.pdf

Author: Victor Stinner
Creation: 11 july 2006
"""

from hachoir.parser import Parser
from hachoir.field import (FieldSet, ParserError,
                                 UInt8, UInt32, UInt64, Enum,
                                 NullBytes, RawBytes, String)
from hachoir.core.endian import LITTLE_ENDIAN, BIG_ENDIAN


class PrimaryVolumeDescriptor(FieldSet):
    static_size = 2041 * 8

    def createFields(self):
        yield NullBytes(self, "unused[]", 1)
        yield String(self, "system_id", 32, "System identifier", strip=" ")
        yield String(self, "volume_id", 32, "Volume identifier", strip=" ")
        yield NullBytes(self, "unused[]", 8)
        yield UInt64(self, "space_size", "Volume space size")
        yield NullBytes(self, "unused[]", 32)
        yield UInt32(self, "set_size", "Volume set size")
        yield UInt32(self, "seq_num", "Sequence number")
        yield UInt32(self, "block_size", "Block size")
        yield UInt64(self, "path_table_size", "Path table size")
        yield UInt32(self, "occu_lpath", "Location of Occurrence of Type L Path Table")
        yield UInt32(self, "opt_lpath", "Location of Optional of Type L Path Table")
        yield UInt32(self, "occu_mpath", "Location of Occurrence of Type M Path Table")
        yield UInt32(self, "opt_mpath", "Location of Optional of Type M Path Table")
        yield RawBytes(self, "root", 34, "Directory Record for Root Directory")
        yield String(self, "vol_set_id", 128, "Volume set identifier", strip=" ")
        yield String(self, "publisher", 128, "Publisher identifier", strip=" ")
        yield String(self, "data_preparer", 128, "Data preparer identifier", strip=" ")
        yield String(self, "application", 128, "Application identifier", strip=" ")
        yield String(self, "copyright", 37, "Copyright file identifier", strip=" ")
        yield String(self, "abstract", 37, "Abstract file identifier", strip=" ")
        yield String(self, "biographic", 37, "Biographic file identifier", strip=" ")
        yield String(self, "creation_ts", 17, "Creation date and time", strip=" ")
        yield String(self, "modification_ts", 17, "Modification date and time", strip=" ")
        yield String(self, "expiration_ts", 17, "Expiration date and time", strip=" ")
        yield String(self, "effective_ts", 17, "Effective date and time", strip=" ")
        yield UInt8(self, "struct_ver", "Structure version")
        yield NullBytes(self, "unused[]", 1)
        yield String(self, "app_use", 512, "Application use", strip=" \0")
        yield NullBytes(self, "unused[]", 653)


class BootRecord(FieldSet):
    static_size = 2041 * 8

    def createFields(self):
        yield String(self, "sys_id", 31, "Boot system identifier", strip="\0")
        yield String(self, "boot_id", 31, "Boot identifier", strip="\0")
        yield RawBytes(self, "system_use", 1979, "Boot system use")


class Terminator(FieldSet):
    static_size = 2041 * 8

    def createFields(self):
        yield NullBytes(self, "null", 2041)


class Volume(FieldSet):
    endian = BIG_ENDIAN
    TERMINATOR = 255
    type_name = {
        0: "Boot Record",
        1: "Primary Volume Descriptor",
        2: "Supplementary Volume Descriptor",
        3: "Volume Partition Descriptor",
        TERMINATOR: "Volume Descriptor Set Terminator",
    }
    static_size = 2048 * 8
    content_handler = {
        0: BootRecord,
        1: PrimaryVolumeDescriptor,
        TERMINATOR: Terminator,
    }

    def createFields(self):
        yield Enum(UInt8(self, "type", "Volume descriptor type"), self.type_name)
        yield RawBytes(self, "signature", 5, "ISO 9960 signature (CD001)")
        if self["signature"].value != b"CD001":
            raise ParserError("Invalid ISO 9960 volume signature")
        yield UInt8(self, "version", "Volume descriptor version")
        cls = self.content_handler.get(self["type"].value, None)
        if cls:
            yield cls(self, "content")
        else:
            yield RawBytes(self, "raw_content", 2048 - 7)


class ISO9660(Parser):
    endian = LITTLE_ENDIAN
    MAGIC = b"\x01CD001"
    NULL_BYTES = 0x8000
    PARSER_TAGS = {
        "id": "iso9660",
        "category": "file_system",
        "description": "ISO 9660 file system",
        "min_size": (NULL_BYTES + 6) * 8,
        "magic": ((MAGIC, NULL_BYTES * 8),),
    }

    def validate(self):
        if self.stream.readBytes(self.NULL_BYTES * 8, len(self.MAGIC)) != self.MAGIC:
            return "Invalid signature"
        return True

    def createFields(self):
        yield self.seekByte(self.NULL_BYTES, null=True)

        while True:
            volume = Volume(self, "volume[]")
            yield volume
            if volume["type"].value == Volume.TERMINATOR:
                break

        if self.current_size < self._size:
            yield self.seekBit(self._size, "end")