// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package xcoff

import (
	
	
	
	
	
	
)

const (
	SAIAMAG   = 0x8
	AIAFMAG   = "`\n"
	AIAMAG    = "<aiaff>\n"
	AIAMAGBIG = "<bigaf>\n"

	// Sizeof
	FL_HSZ_BIG = 0x80
	AR_HSZ_BIG = 0x70
)

type bigarFileHeader struct {
	Flmagic    [SAIAMAG]byte // Archive magic string
	Flmemoff   [20]byte      // Member table offset
	Flgstoff   [20]byte      // 32-bits global symtab offset
	Flgst64off [20]byte      // 64-bits global symtab offset
	Flfstmoff  [20]byte      // First member offset
	Fllstmoff  [20]byte      // Last member offset
	Flfreeoff  [20]byte      // First member on free list offset
}

type bigarMemberHeader struct {
	Arsize   [20]byte // File member size
	Arnxtmem [20]byte // Next member pointer
	Arprvmem [20]byte // Previous member pointer
	Ardate   [12]byte // File member date
	Aruid    [12]byte // File member uid
	Argid    [12]byte // File member gid
	Armode   [12]byte // File member mode (octal)
	Arnamlen [4]byte  // File member name length
	// _ar_nam is removed because it's easier to get name without it.
}

// Archive represents an open AIX big archive.
type Archive struct {
	ArchiveHeader
	Members []*Member

	closer io.Closer
}

// MemberHeader holds information about a big archive file header
type ArchiveHeader struct {
	magic string
}

// Member represents a member of an AIX big archive.
type Member struct {
	MemberHeader
	sr *io.SectionReader
}

// MemberHeader holds information about a big archive member
type MemberHeader struct {
	Name string
	Size uint64
}

// OpenArchive opens the named archive using os.Open and prepares it for use
// as an AIX big archive.
func ( string) (*Archive, error) {
	,  := os.Open()
	if  != nil {
		return nil, 
	}
	,  := NewArchive()
	if  != nil {
		.Close()
		return nil, 
	}
	.closer = 
	return , nil
}

// Close closes the Archive.
// If the Archive was created using NewArchive directly instead of OpenArchive,
// Close has no effect.
func ( *Archive) () error {
	var  error
	if .closer != nil {
		 = .closer.Close()
		.closer = nil
	}
	return 
}

// NewArchive creates a new Archive for accessing an AIX big archive in an underlying reader.
func ( io.ReaderAt) (*Archive, error) {
	 := func( []byte) (int64, error) {
		return strconv.ParseInt(strings.TrimSpace(string()), 10, 64)
	}
	 := io.NewSectionReader(, 0, 1<<63-1)

	// Read File Header
	var  [SAIAMAG]byte
	if ,  := .ReadAt([:], 0);  != nil {
		return nil, 
	}

	 := new(Archive)
	switch string([:]) {
	case AIAMAGBIG:
		.magic = string([:])
	case AIAMAG:
		return nil, fmt.Errorf("small AIX archive not supported")
	default:
		return nil, fmt.Errorf("unrecognised archive magic: 0x%x", )
	}

	var  bigarFileHeader
	if ,  := .Seek(0, io.SeekStart);  != nil {
		return nil, 
	}
	if  := binary.Read(, binary.BigEndian, &);  != nil {
		return nil, 
	}

	,  := (.Flfstmoff[:])
	if  != nil {
		return nil, fmt.Errorf("error parsing offset of first member in archive header(%q); %v", , )
	}

	if  == 0 {
		// Occurs if the archive is empty.
		return , nil
	}

	,  := (.Fllstmoff[:])
	if  != nil {
		return nil, fmt.Errorf("error parsing offset of first member in archive header(%q); %v", , )
	}

	// Read members
	for {
		// Read Member Header
		// The member header is normally 2 bytes larger. But it's easier
		// to read the name if the header is read without _ar_nam.
		// However, AIAFMAG must be read afterward.
		if ,  := .Seek(, io.SeekStart);  != nil {
			return nil, 
		}

		var  bigarMemberHeader
		if  := binary.Read(, binary.BigEndian, &);  != nil {
			return nil, 
		}

		 := new(Member)
		.Members = append(.Members, )

		,  := (.Arsize[:])
		if  != nil {
			return nil, fmt.Errorf("error parsing size in member header(%q); %v", , )
		}
		.Size = uint64()

		// Read name
		,  := (.Arnamlen[:])
		if  != nil {
			return nil, fmt.Errorf("error parsing name length in member header(%q); %v", , )
		}
		 := make([]byte, )
		if  := binary.Read(, binary.BigEndian, );  != nil {
			return nil, 
		}
		.Name = string()

		 :=  + AR_HSZ_BIG + 
		if &1 != 0 {
			++
			if ,  := .Seek(1, io.SeekCurrent);  != nil {
				return nil, 
			}
		}

		// Read AIAFMAG string
		var  [2]byte
		if  := binary.Read(, binary.BigEndian, &);  != nil {
			return nil, 
		}
		if string([:]) != AIAFMAG {
			return nil, fmt.Errorf("AIAFMAG not found after member header")
		}

		 += 2 // Add the two bytes of AIAFMAG
		.sr = io.NewSectionReader(, , )

		if  ==  {
			break
		}
		,  = (.Arnxtmem[:])
		if  != nil {
			return nil, fmt.Errorf("error parsing offset of first member in archive header(%q); %v", , )
		}

	}

	return , nil
}

// GetFile returns the XCOFF file defined by member name.
// FIXME: This doesn't work if an archive has two members with the same
// name which can occur if an archive has both 32-bits and 64-bits files.
func ( *Archive) ( string) (*File, error) {
	for ,  := range .Members {
		if .Name ==  {
			return NewFile(.sr)
		}
	}
	return nil, fmt.Errorf("unknown member %s in archive", )
}