from filetypes.base import *
import struct

# https://learn.microsoft.com/en-us/openspecs/office_file_formats/ms-onestore/2b394c6b-8788-441f-b631-da1583d772fd

class Header(Struct):

    def parse(self):
        yield GUID(name="FileType")
        yield GUID(name="File")
        yield GUID(name="LegacyFileVersion")
        yield GUID(name="FileFormat")
        yield UInt32(name="LastCodeThatWroteToThisFile", values=[
            (".one", 0x2A),
            (".onetoc2", 0x1B),
        ])
        yield UInt32(name="OldestCodeThatHasWrittenToThisFile", values=[
            (".one", 0x2A),
            (".onetoc2", 0x1B),
        ])
        yield UInt32(name="NewestCodeThatHasWrittenToThisFile", values=[
            (".one", 0x2A),
            (".onetoc2", 0x1B),
        ])
        yield UInt32(name="OldestCodeThatMayReadThisFile", values=[
            (".one", 0x2A),
            (".onetoc2", 0x1B),
        ])
        yield FileChunkReference32(name="LegacyFreeChunkList", comment="unused")
        yield FileChunkReference32(name="LegacyTransactionLog", comment="unused")
        yield UInt32(name="NumberOfTransactionsInLog", comment="number of transactions in the transaction log")
        yield UInt32(name="LegacyExpectedFileLength", comment="unused")
        yield Unused(8)
        yield FileChunkReference32(name="LegacyFileNodeListRoot", comment="unused")
        yield UInt32(name="LegacyFreeSpaceInFreeChunkList", comment="unused")
        yield UInt8(name="NeedsDefrag", comment="unused")
        yield UInt8(name="RepairedFile", comment="unused")
        yield UInt8(name="NeedsGarbageCollect", comment="unused")
        yield UInt8(name="HasNoEmbeddedFileObjects", comment="unused")
        yield GUID(name="Ancestor")
        yield UInt32(name="NameCrc", comment="CRC value of the name of this revision store file")
        yield FileChunkReference64x32(name="HashedChunkList", comment="a reference to the first FileNodeListFragment in a hashed chunk list")
        yield FileChunkReference64x32(name="TransactionLog", comment="a reference to the first TransactionLogFragment structure")
        yield FileChunkReference64x32(name="FileNodeListRoot", comment="a reference to a root file node list")
        yield FileChunkReference64x32(name="FreeChunkList", comment="a reference to the first FreeChunkListFragment structure")
        yield UInt64(name="ExpectedFileLength", comment="the size, in bytes, of this revision store file")
        yield UInt64(name="FreeSpaceInFreeChunkList", comment="the size, in bytes, of the free space specified by the free chunk list")
        yield GUID(name="FileVersion", comment="when either the value of cTransactionsInLog field or the guidDenyReadFileVersion field is being changed, guidFileVersion MUST be changed to a new GUID")
        yield UInt64(name="FileVersionGeneration", comment="the number of times the file has changed")
        yield GUID(name="DenyReadFileVersion", comment="when the existing contents of the file are being changed, excluding the Header structure of the file and unused storage blocks, DenyReadFileVersion MUST be changed to a new GUID")
        yield UInt32(name="DebugLogFlags", comment="unused")
        yield FileChunkReference64x32(name="DebugLog", comment="unused")
        yield FileChunkReference64x32(name="AllocVerificationFreeChunkList", comment="unused")
        yield UInt32(name="Created", comment="the build number of the application that created this revision store file")
        yield UInt32(name="LastWroteToThisFile", comment="the build number of the application that last wrote to this revision store file")
        yield UInt32(name="OldestWritten", comment="the build number of the oldest application that wrote to this revision store file")
        yield UInt32(name="NewestWritten", comment="the build number of the newest application that wrote to this revision store file")
        yield Unused(728)


class ExtendedGuid(Struct):

    def parse(self):
        yield GUID(name="GUID")
        yield UInt32(name="n")


class FileChunkReference32(Struct):

    def parse(self):
        yield Offset32(name="Offset", zero_is_invalid=True, comment="specifies the location of the referenced data in the file")
        yield UInt32(name="Size", comment="specifies the size, in bytes, of the referenced data")


class FileChunkReference64x32(Struct):

    def parse(self):
        yield Offset64(name="Offset", zero_is_invalid=True, comment="specifies the location of the referenced data in the file")
        yield UInt32(name="Size", comment="specifies the size, in bytes, of the referenced data")


class FileNodeListHeader(Struct):

    def parse(self):
        magic = yield UInt64(name="Magic", comment="should be 0xA4567AB1F5F7F4C4")
        if magic != 0xA4567AB1F5F7F4C4:
            raise FatalError("invalid FileNodeListHeader magic")
        yield UInt32(name="FileNodeListId", comment="identity of the file node list")
        yield UInt32(name="FragmentSequence", comment="index of the fragment in the file node list containing the fragment")


class CompactId(Struct):

    def parse(self):
        yield UInt8(name="N", comment="value of the ExtendedGUID.n field")
        yield UInt24(name="GuidIndex", comment="index in the global identification table")        


class FileNodeList(Struct):

    def __init__(self, max_size, **kwargs):
        Struct.__init__(self, **kwargs)
        self.max_size = max_size

    def parse(self):
        yield FileNodeListHeader()
        while True:
            if len(self) + 20 + 3 >= self.max_size:
                break
            next_dword, = struct.unpack("<I", self.look_ahead(4))
            name = {
                    0x000: "EmptyNode",
                    0x004: "ObjectSpaceManifestRootFND",
                    0x008: "ObjectSpaceManifestListReferenceFND",
                    0x00C: "ObjectSpaceManifestListStartFND",
                    0x010: "RevisionManifestListReferenceFND",
                    0x014: "RevisionManifestListStartFND",
                    0x01B: "RevisionManifestStart4FND",
                    0x01C: "RevisionManifestEndFND",
                    0x01E: "RevisionManifestStart6FND",
                    0x01F: "RevisionManifestStart7FND",
                    0x021: "GlobalIdTableStartFNDX",
                    0x022: "GlobalIdTableStart2FND",
                    0x024: "GlobalIdTableEntryFNDX",
                    0x025: "GlobalIdTableEntry2FNDX",
                    0x026: "GlobalIdTableEntry3FNDX",
                    0x028: "GlobalIdTableEndFNDX",
                    0x02D: "ObjectDeclarationWithRefCountFNDX",
                    0x02E: "ObjectDeclarationWithRefCount2FNDX",
                    0x041: "ObjectRevisionWithRefCountFNDX",
                    0x042: "ObjectRevisionWithRefCount2FNDX",
                    0x059: "RootObjectReference2FNDX",
                    0x05A: "RootObjectReference3FND",
                    0x05C: "RevisionRoleDeclarationFND",
                    0x05D: "RevisionRoleAndContextDeclarationFND",
                    0x072: "ObjectDeclarationFileData3RefCountFND",
                    0x073: "ObjectDeclarationFileData3LargeRefCountFND",
                    0x07C: "ObjectDataEncryptionKeyV2FNDX",
                    0x084: "ObjectInfoDependencyOverridesFND",
                    0x08C: "DataSignatureGroupDefinitionFND",
                    0x090: "FileDataStoreListReferenceFND",
                    0x094: "FileDataStoreObjectReferenceFND",
                    0x0A4: "ObjectDeclaration2RefCountFND",
                    0x0A5: "ObjectDeclaration2LargeRefCountFND",
                    0x0B0: "ObjectGroupListReferenceFND",
                    0x0B4: "ObjectGroupStartFND",
                    0x0B8: "ObjectGroupEndFND",
                    0x0C2: "HashedChunkDescriptor2FND",
                    0x0C4: "ReadOnlyObjectDeclaration2RefCountFND",
                    0x0C5: "ReadOnlyObjectDeclaration2LargeRefCountFND",
                    0x0FF: "Terminator",
            }.get(next_dword & 0x3ff, "FileNode")
            n = yield FileNode(name = name)
            n_id = n["IdAndSize"] & 0x3ff
            if n_id == 0xFF:
                break
        if len(self) + 20 < self.max_size:
            yield Unused(self.max_size - (len(self) + 20))
        yield FileChunkReference64x32(name="NextFragment", comment="location and size of the next fragment")
        magic = yield UInt64(name="Footer", comment="should be 0x8BC215C38233BA4B")
        if magic != 0x8BC215C38233BA4B:
            raise FatalError("invalid FileNodeList footer magic")


class FileChunkReference64x32(Struct):

    def parse(self):
        yield Offset64(name="Offset", zero_is_invalid=True, comment="location of the referenced data in the file")
        yield UInt32(name="Size", comment="size, in bytes, of the referenced data")


class FileChunkReference(Struct):

    def __init__(self, offset_format, sz_format, **kwargs):
        Struct.__init__(self, **kwargs)
        self.offset_format = offset_format
        self.sz_format = sz_format

    def parse(self):
        if self.offset_format == 0:
            yield Offset64(name="Offset", zero_is_invalid=True, comment="location of the referenced data in the file")
        elif self.offset_format == 1:
            yield Offset32(name="Offset", zero_is_invalid=True, comment="location of the referenced data in the file")
        elif self.offset_format == 2:
            yield UInt16(name="OffsetCompressed", comment="location of the referenced data in the file / 8")
        elif self.offset_format == 3:
            yield UInt32(name="OffsetCompressed", comment="location of the referenced data in the file / 8")
        else:
            raise ValueError("Invalid offset format")
        if self.sz_format == 0:
            yield UInt32(name="Size", comment="size, in bytes, of the referenced data")
        elif self.sz_format == 1:
            yield UInt64(name="Size", comment="size, in bytes, of the referenced data")
        elif self.sz_format == 2:
            yield UInt8(name="SizeCompressed", comment="size, in bytes, of the referenced data / 8")
        elif self.sz_format == 3:
            yield UInt16(name="SizeCompressed", comment="size, in bytes, of the referenced data / 8")
        else:
            raise ValueError("Invalid offset format")

    @staticmethod
    def unpack(ref):
        if "Offset" in ref:
            offset = ref["Offset"]
        else:
            offset = ref["OffsetCompressed"] * 8
        if "Size" in ref:
            size = ref["Size"]
        else:
            size = ref["SizeCompressed"] * 8
        return offset, size


class FileChunkReferenceWithExGuid(FileChunkReference):

    def parse(self):
        yield from FileChunkReference.parse(self)
        yield ExtendedGuid()


class ExGuidAndInstance(Struct):

    def parse(self):
        yield ExtendedGuid()        
        yield UInt32(name="Instance", comment="ignored")


class ExGuidAndRole(Struct):

    def parse(self):
        yield ExtendedGuid()        
        yield UInt32(name="Role", comment="role")        


class FileChunkReferenceWithGuid(FileChunkReference):

    def parse(self):
        yield from FileChunkReference.parse(self)
        yield GUID(name="Guid")        


class FileChunkReferenceObject(FileChunkReference):

    def parse(self):
        yield from FileChunkReference.parse(self)
        yield CompactId(name="Identifier")              
        yield UInt16(name="Type", values=JCID_INDEX_VALUES)
        yield BitsField(
            Bit(name="Binary", comment="contains encryption data transmitted over the File Synchronization via SOAP over HTTP Protocol"),
            Bit(name="IsPropertySet", comment="object contains a property set"),
            Bit(name="IsGraphNode", comment="undefined"),
            Bit(name="IsFileData", comment="object is a file data object"),
            Bit(name="IsReadOnly", comment="object is a read only"),
            NullBits(11),
            Bit(name="HasOidReference", comment="object contains references to other objects"),
            Bit(name="HasOsidReferences", comment="object contains references to object spaces"),
            NullBits(6),
            name="Flags", comment="")
        yield UInt8(name="ReferenceCount")


class FileChunkReferenceFile(Struct):

    def parse(self):
        yield CompactId(name="Identifier")              
        yield UInt16(name="Type", values=JCID_INDEX_VALUES)
        yield BitsField(
            Bit(name="Binary", comment="contains encryption data transmitted over the File Synchronization via SOAP over HTTP Protocol"),
            Bit(name="IsPropertySet", comment="object contains a property set"),
            Bit(name="IsGraphNode", comment="undefined"),
            Bit(name="IsFileData", comment="object is a file data object"),
            Bit(name="IsReadOnly", comment="object is a read only"),
            NullBits(11),
            name="Flags", comment="")
        yield UInt8(name="ReferenceCount")
        yield UnicodeString(name="Target")
        yield UnicodeString(name="Extension")


class FileNode(Struct):

    def parse(self):
        v = yield UInt32(name="IdAndSize")
        typ = v & 0x3FF
        sz = (v >> 10) & 0x1FFF
        basetype = (v >> 27) & 0xF
        scp_format = (v>>23) & 3
        sz_format = (v>>25) & 3
        name = ""
        ff = {
            0x4: ExtendedGuid(),
            0x8: FileChunkReferenceWithExGuid(scp_format, sz_format),
            0xC: ExtendedGuid(),
            0x14: ExGuidAndInstance(),
            0x5a: ExGuidAndRole(),
            0x72: FileChunkReferenceFile(),
            0x94: FileChunkReferenceWithGuid(scp_format, sz_format),
            0xa4: FileChunkReferenceObject(scp_format, sz_format),
            0xb0: FileChunkReferenceWithExGuid(scp_format, sz_format),
            0xb4: ExtendedGuid(),
        }.get(typ)
        if ff is None and basetype in (1,2):
            ff = FileChunkReference(scp_format, sz_format)
            if basetype == 1:
                name = "FileNodeReference"
            else:
                name = "FileNodeListReference"
        if ff is not None:
            if name:
                ff.name = name
            yield ff
        if sz > len(self):
            yield Bytes(sz - len(self), name="Data")


class FileDataStoreObject(Struct):

    def parse(self):
        magic = yield GUID(name="Magic")
        if magic != "BDE316E7-2665-4511-A4C4-8D4D0B7A9EAC":
            raise FatalError("Invalid FileDataStoreObject magic {} at #{:x}".format(magic, self.parser.tell()))
        sz = yield UInt64(name="FileSize")
        yield Unused(12)
        yield Bytes(sz, name="Data")
        yield Align(8)
        magic = yield GUID(name="FooterMagic")
        if magic != "71FBA722-0F79-4A0B-BB13-899256426B24":
            raise FatalError("Invalid FileDataStoreObject footer magic")


class ObjectSpaceObjectStreamOfOIDs(Struct):

    def parse(self):
        count = yield UInt24(name="Count")
        yield BitsField(
            NullBits(6),
            Bit(name="ExtendedStreamsPresent", comment="additional streams of data follows this stream of data"),
            Bit(name="OsidStreamNotPresent", comment="structure does not contain OSIDs or ContextIDs fields"),
            name="Flags", comment="")
        if count > 0:
            yield Array(count, CompactId(), name="IdList")

    
class ObjectSpaceObjectPropSet(Struct):

    def parse(self):
        hdr = yield ObjectSpaceObjectStreamOfOIDs(name="ObjectIds", comment="list of objects that are referenced by this ObjectSpaceObjectPropSet")
        if not hdr["Flags"]["OsidStreamNotPresent"]:
            osids = yield ObjectSpaceObjectStreamOfOIDs(name="ObjectSpaceIds", comment="list of object spaces referenced by this ObjectSpaceObjectPropSet")
            if osids["Flags"]["ExtendedStreamsPresent"]:
                yield ObjectSpaceObjectStreamOfOIDs(name="ContextIds", comment="list of object spaces referenced by this ObjectSpaceObjectPropSet")
        yield PropertySet()
        if len(self) % 8:
            yield Unused(8 - (len(self) % 8))


class VarData(Struct):

    def parse(self):
        sz = yield UInt32(name="Size", comment="data size")
        if sz:
            yield Bytes(sz, name="Data")

class VarString(Struct):

    def parse(self):
        sz = yield UInt32(name="Size", comment="data size")
        if sz:
            yield StringUtf16le(sz // 2, name="String")            

class VarGuid(Struct):

    def parse(self):
        sz = yield UInt32(name="Size", comment="data size")
        yield GUID(name="Guid")                      

class ArrayOfPropertyValues(Struct):

    def parse(self):
        sz = yield UInt32(name="Count", comment="number of properties")
        if sz > 0:
            yield UInt32(name="PropertyType")
            for i in range(sz):
                yield PropertySet(name="Property")

            

class PropertySet(Struct):

    def parse(self):
        sz = yield UInt16(name="Count", comment="number of properties")
        if sz:
            types = yield Array(sz, UInt32(name="PropetyId", values=PROPERTIES_IDS), name="TypeAndIdentifiers")
            for i, t in enumerate(types):
                data_type = {
                    3: UInt8,
                    4: UInt16,
                    5: UInt32,
                    6: UInt64,
                    7: VarData,
                    9: UInt32,
                    11: UInt32,
                    13: UInt32,
                    16: ArrayOfPropertyValues,
                    17: PropertySet,
                }.get((t.value >> 26) & 0x1F)
                values = []
                if data_type is not None and t.has_enum:
                    n = t.enum
                    if data_type == UInt64:
                        if "Stamp" in n:
                            data_type = Filetime
                    elif data_type == UInt16: 
                        if "LangID" in n:
                            values = LanguageId.ALL
                    elif data_type == UInt32: 
                        if n.endswith("Time") or n.endswith("Stamp"):
                            data_type = DosDateTime
                    elif data_type == VarData:
                        sz, = struct.unpack("<I", self.look_ahead(4))
                        if sz == 0x10 and "Guid" in n:
                            data_type = VarGuid
                        elif ("String" in n or "Author" in n or "Unicode" in n or "path" in n or "Name" in n) and sz % 2 == 0:
                            data_type = VarString
                        
                if data_type is not None:
                    yield data_type(name=f"Property[{i}].Value", values=values)

# https://learn.microsoft.com/en-us/openspecs/office_file_formats/ms-one/e9bf7da8-7aab-4668-be5e-e0c421175e3c
PROPERTIES_IDS = [
    ("LayoutTightLayout", 0x08001C00),
    ("PageWidth", 0x14001C01),
    ("PageHeight", 0x14001C02),
    ("OutlineElementChildLevel", 0x0C001C03),
    ("Bold", 0x08001C04),
    ("Italic", 0x08001C05),
    ("Underline", 0x08001C06),
    ("Strikethrough", 0x08001C07),
    ("Superscript", 0x08001C08),
    ("Subscript", 0x08001C09),
    ("Font", 0x1C001C0A),
    ("FontSize", 0x10001C0B),
    ("FontColor", 0x14001C0C),
    ("Highlight", 0x14001C0D),
    ("RgOutlineIndentDistance", 0x1C001C12),
    ("BodyTextAlignment", 0x0C001C13),
    ("OffsetFromParentHoriz", 0x14001C14),
    ("OffsetFromParentVert", 0x14001C15),
    ("NumberListFormat", 0x1C001C1A),
    ("LayoutMaxWidth", 0x14001C1B),
    ("LayoutMaxHeight", 0x14001C1C),
    ("ContentChildNodesOfOutlineElement", 0x24001C1F),
    ("ContentChildNodesOfPageManifest", 0x24001C1F),
    ("ElementChildNodesOfSection", 0x24001C20),
    ("ElementChildNodesOfPage", 0x24001C20),
    ("ElementChildNodesOfTitle", 0x24001C20),
    ("ElementChildNodesOfOutline", 0x24001C20),
    ("ElementChildNodesOfOutlineElement", 0x24001C20),
    ("ElementChildNodesOfTable", 0x24001C20),
    ("ElementChildNodesOfTableRow", 0x24001C20),
    ("ElementChildNodesOfTableCell", 0x24001C20),
    ("ElementChildNodesOfVersionHistory", 0x24001C20),
    ("EnableHistory", 0x08001E1E),
    ("RichEditTextUnicode", 0x1C001C22),
    ("ListNodes", 0x24001C26),
    ("NotebookManagementEntityGuid", 0x1C001C30),
    ("OutlineElementRTL", 0x08001C34),
    ("LanguageID", 0x14001C3B),
    ("LayoutAlignmentInParent", 0x14001C3E),
    ("PictureContainer", 0x20001C3F),
    ("PageMarginTop", 0x14001C4C),
    ("PageMarginBottom", 0x14001C4D),
    ("PageMarginLeft", 0x14001C4E),
    ("PageMarginRight", 0x14001C4F),
    ("ListFont", 0x1C001C52),
    ("TopologyCreationTimeStamp", 0x18001C65),
    ("LayoutAlignmentSelf", 0x14001C84),
    ("IsTitleTime", 0x08001C87),
    ("IsBoilerText", 0x08001C88),
    ("PageSize", 0x14001C8B),
    ("PortraitPage", 0x08001C8E),
    ("EnforceOutlineStructure", 0x08001C91),
    ("EditRootRTL", 0x08001C92),
    ("CannotBeSelected", 0x08001CB2),
    ("IsTitleText", 0x08001CB4),
    ("IsTitleDate", 0x08001CB5),
    ("ListRestart", 0x14001CB7),
    ("IsLayoutSizeSetByUser", 0x08001CBD),
    ("ListSpacingMu", 0x14001CCB),
    ("LayoutOutlineReservedWidth", 0x14001CDB),
    ("LayoutResolveChildCollisions", 0x08001CDC),
    ("IsReadOnly", 0x08001CDE),
    ("LayoutMinimumOutlineWidth", 0x14001CEC),
    ("LayoutCollisionPriority", 0x14001CF1),
    ("CachedTitleString", 0x1C001CF3),
    ("DescendantsCannotBeMoved", 0x08001CF9),
    ("RichEditTextLangID", 0x10001CFE),
    ("LayoutTightAlignment", 0x08001CFF),
    ("Charset", 0x0C001D01),
    ("CreationTimeStamp", 0x14001D09),
    ("Deletable", 0x08001D0C),
    ("ListMSAAIndex", 0x10001D0E),
    ("IsBackground", 0x08001D13),
    ("IRecordMedia", 0x14001D24),
    ("CachedTitleStringFromPage", 0x1C001D3C),
    ("RowCount", 0x14001D57),
    ("ColumnCount", 0x14001D58),
    ("TableBordersVisible", 0x08001D5E),
    ("StructureElementChildNodes", 0x24001D5F),
    ("ChildGraphSpaceElementNodes", 0x2C001D63),
    ("TableColumnWidths", 0x1C001D66),
    ("Author", 0x1C001D75),
    ("LastModifiedTimeStamp", 0x18001D77),
    ("AuthorOriginal", 0x20001D78),
    ("AuthorMostRecent", 0x20001D79),
    ("LastModifiedTime", 0x14001D7A),
    ("IsConflictPage", 0x08001D7C),
    ("TableColumnsLocked", 0x1C001D7D),
    ("SchemaRevisionInOrderToRead", 0x14001D82),
    ("IsConflictObjectForRender", 0x08001D96),
    ("EmbeddedFileContainer", 0x20001D9B),
    ("EmbeddedFileName", 0x1C001D9C),
    ("SourceFilepath", 0x1C001D9D),
    ("ConflictingUserName", 0x1C001D9E),
    ("ImageFilename", 0x1C001DD7),
    ("IsConflictObjectForSelection", 0x08001DDB),
    ("PageLevel", 0x14001DFF),
    ("TextRunIndex", 0x1C001E12),
    ("TextRunFormatting", 0x24001E13),
    ("Hyperlink", 0x08001E14),
    ("UnderlineType", 0x0C001E15),
    ("Hidden", 0x08001E16),
    ("HyperlinkProtected", 0x08001E19),
    ("TextRunIsEmbeddedObject", 0x08001E22),
    ("CellShadingColor", 0x14001e26),
    ("ImageAltText", 0x1C001E58),
    ("MathFormatting", 0x08003401),
    ("ParagraphStyle", 0x2000342C),
    ("ParagraphSpaceBefore", 0x1400342E),
    ("ParagraphSpaceAfter", 0x1400342F),
    ("ParagraphLineSpacingExact", 0x14003430),
    ("MetaDataObjectsAboveGraphSpace", 0x24003442),
    ("TextRunDataObject", 0x24003458),
    ("TextRunData", 0x40003499),
    ("ParagraphStyleId", 0x1C00345A),
    ("HasVersionPages", 0x08003462),
    ("ActionItemType", 0x10003463),
    ("NoteTagShape", 0x10003464),
    ("NoteTagHighlightColor", 0x14003465),
    ("NoteTagTextColor", 0x14003466),
    ("NoteTagPropertyStatus", 0x14003467),
    ("NoteTagLabel", 0x1C003468),
    ("NoteTagCreated", 0x1400346E),
    ("NoteTagCompleted", 0x1400346F),
    ("NoteTagDefinitionOid", 0x20003488),
    ("NoteTagStates", 0x04003489),
    ("ActionItemStatus", 0x10003470),
    ("ActionItemSchemaVersion", 0x0C003473),
    ("ReadingOrderRTL", 0x08003476),
    ("ParagraphAlignment", 0x0C003477),
    ("VersionHistoryGraphSpaceContextNodes", 0x3400347B),
    ("DisplayedPageNumber", 0x14003480),
    ("SectionDisplayName", 0x1C00349B),
    ("NextStyle", 0x1C00348A),
    ("WebPictureContainer14", 0x200034C8),
    ("ImageUploadState", 0x140034CB),
    ("TextExtendedAscii", 0x1C003498),
    ("PictureWidth", 0x140034CD),
    ("PictureHeight", 0x140034CE),
    ("PageMarginOriginX", 0x14001D0F),
    ("PageMarginOriginY", 0x14001D10),
    ("WzHyperlinkUrl", 0x1C001E20),
    ("TaskTagDueDate", 0x1400346B),
    ("IsDeletedGraphSpaceContent", 0x1C001DE9),
]


JCID_INDEX_VALUES = [
        ("EmbeddedFileContainer", 0x36),
        ("PictureContainer14", 0x39),
        ("AuthorOrToc", 0x0001),
        ("PersistablePropertyContainerForTOC", 0x0001),
        ("PersistablePropertyContainerForTOCSection", 0x0001),
        ("SectionNode", 0x0007),
        ("PageSeriesNode", 0x0008),
        ("PageNode", 0x000B),
        ("OutlineNode", 0x000C),
        ("OutlineElementNode", 0x000D),
        ("RichTextOENode", 0x000E),
        ("ImageNode", 0x0011),
        ("NumberListNode", 0x0012),
        ("OutlineGroup", 0x0019),
        ("TableNode", 0x0022),
        ("TableRowNode", 0x0023),
        ("TableCellNode", 0x0024),
        ("TitleNode", 0x002C),
        ("PageMetaData", 0x0030),
        ("SectionMetaData", 0x0031),
        ("EmbeddedFileNode", 0x0035),
        ("PageManifestNode", 0x0037),
        ("ConflictPageMetaData", 0x0038),
        ("VersionHistoryContent", 0x003C),
        ("VersionProxy", 0x003D),
        ("NoteTagSharedDefinitionContainer", 0x0043),
        ("RevisionMetaData", 0x0044),
        ("VersionHistoryMetaData", 0x0046),
        ("ParagraphStyleObject", 0x004D),
        ("ParagraphStyleObjectForText", 0x004D),
]


class OneAnalyzer(FileTypeAnalyzer):
    category = malcat.FileType.DOCUMENT
    name = "ONE"
    regexp = r"\xE4\x52\x5C\x7B\x8C\xD8\xA7\x4D\xAE\xB1\x53\x78\xD0\x29\x96\xD3.{16}\x00{16}\x3F\xDD\x9A\x10\x1B\x91\xF5\x49\xA5\xD0\x17\x91\xED\xC8\xAE\xD8"


    def __init__(self):
        FileTypeAnalyzer.__init__(self)
        self.filesystem = {}


    def parse_node_list(self, ref, name, element_parse_function=None, parent=None):
        while True:
            offset, size = FileChunkReference.unpack(ref)
            if size == 0 or offset == 0xffffffffffffffff:
                break
            self.jump(offset)
            nl = yield FileNodeList(max_size=size, name=name, parent=parent)
            if element_parse_function is not None:
                for node in nl[1:-2]:
                    if node.name not in ("Padding", "EmptyNode"):
                        yield from element_parse_function(node, parent=nl)
            if "NextFragment" in nl:
                ref = nl["NextFragment"]
            else:
                break

    def parse_data_store_element(self, node, parent=None):
        if node.name == "FileDataStoreObjectReferenceFND":
            # files
            offset, size = FileChunkReference.unpack(node["FileChunkReferenceWithGuid"])
            if size == 0:
                return
            guid = node["FileChunkReferenceWithGuid"]["Guid"]
            self.jump(offset)
            f = yield FileDataStoreObject(category=Type.DATA, parent=parent)
            self.filesystem[guid] = f
            self.add_file(guid, f["FileSize"], "open")
        
    def parse_object_space_element(self, node, parent=None):
        #manifest
        if node.name == "RevisionManifestListReferenceFND":
            yield from self.parse_node_list(node["FileNodeListReference"], "Revision", self.parse_object_space_element, parent=parent)
        elif node.name == "ObjectGroupListReferenceFND":
            yield from self.parse_node_list(node["FileChunkReferenceWithExGuid"], "Objects", self.parse_object_space_element, parent=parent)
        elif node.name == "ObjectDeclaration2RefCountFND":
            offset, size = FileChunkReference.unpack(node["FileChunkReferenceObject"])
            self.jump(offset)
            yield ObjectSpaceObjectPropSet(name="ObjectPropertySet", parent=parent)

    def parse_root_element(self, node, parent=None):
        if node.name == "FileDataStoreListReferenceFND":
            yield from self.parse_node_list(node["FileNodeListReference"], "DataStore", self.parse_data_store_element)
        elif node.name == "ObjectSpaceManifestListReferenceFND":
            yield from self.parse_node_list(node["FileChunkReferenceWithExGuid"], "ObjectSpace", self.parse_object_space_element)


    def open(self, vfile, password=None):
        fdso = self.filesystem.get(vfile.path)
        if fdso is None:
            raise KeyError("Cannot find file {}".format(vfile.path))
        return fdso["Data"]

    def parse(self, hint):
        hdr = yield Header()
        body = self.tell()
        self.set_eof(hdr["ExpectedFileLength"])
        self.confirm()

        yield from self.parse_node_list(hdr["FileNodeListRoot"], "FileNodeListRoot", self.parse_root_element)
        self.add_section("nodes", body, hdr["ExpectedFileLength"] - hdr.size)

            
