package oz

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"path"
	"strings"

	"github.com/subgraph/oz/network"
)

type Profile struct {
	// Name of this profile
	Name string
	// Path to binary to launch
	Path string
	// List of path to binaries matching this sandbox
	Paths []string
	// Path of the config file
	ProfilePath string `json:"-"`
	// Optional path of binary to watch for watchdog purposes if different than Path
	Watchdog string
	// Optional wrapper binary to use when launching command (ex: tsocks)
	Wrapper string
	// If true launch one sandbox per instance, otherwise run all instances in same sandbox
	Multi bool
	// Disable mounting of sys and proc inside the sandbox
	NoSysProc bool
	// Disable bind mounting of default directories (etc,usr,bin,lib,lib64)
	// Also disables default blacklist items (/sbin, /usr/sbin, /usr/bin/sudo)
	// Normally not used
	NoDefaults bool
	// Allow bind mounting of files passed as arguments inside the sandbox
	AllowFiles bool `json:"allow_files"`
	// List of paths to bind mount inside jail
	Whitelist []WhitelistItem
	// List of paths to blacklist inside jail
	Blacklist []BlacklistItem
	// Optional XServer config
	XServer XServerConf
	// List of environment variables
	Environment []EnvVar
	// Networking
	Networking NetworkProfile
	// Seccomp
	Seccomp SeccompConf
}

type AudioMode string

const (
	PROFILE_AUDIO_NONE    AudioMode = "none"
	PROFILE_AUDIO_SPEAKER AudioMode = "speaker"
	PROFILE_AUDIO_FULL    AudioMode = "full"
)

type XServerConf struct {
	Enabled             bool
	TrayIcon            string    `json:"tray_icon"`
	WindowIcon          string    `json:"window_icon"`
	EnableTray          bool      `json:"enable_tray"`
	EnableNotifications bool      `json:"enable_notifications"`
	UsePulseAudio       bool      `json:"use_pulse_audio"`
	DisableClipboard    bool      `json:"disable_clipboard"`
	AudioMode           AudioMode `json:"audio_mode"`
}

type SeccompConf struct {
	Mode              string
	Enforce           bool
	Seccomp_Whitelist string
	Seccomp_Blacklist string
}

type WhitelistItem struct {
	Path     string
	ReadOnly bool `json:"read_only"`
}

type BlacklistItem struct {
	Path string
}

type EnvVar struct {
	Name  string
	Value string
}

// Sandbox network definition
type NetworkProfile struct {
	// One of empty, host, bridge
	Nettype network.NetType `json:"type"`

	// Name of the bridge to attach to
	//Bridge string

	// List of Sockets we want to attach to the jail
	//  Applies to Nettype: bridge and empty only
	Sockets []network.ProxyConfig
}

const defaultProfileDirectory = "/var/lib/oz/cells.d"

var loadedProfiles []*Profile

type Profiles []*Profile

func NewDefaultProfile() *Profile {
	return &Profile{
		Multi:      false,
		AllowFiles: false,
		XServer: XServerConf{
			Enabled:             true,
			EnableTray:          false,
			EnableNotifications: false,
			UsePulseAudio:       false,
			AudioMode:           PROFILE_AUDIO_NONE,
		},
	}
}

func (ps Profiles) GetProfileByName(name string) (*Profile, error) {
	if loadedProfiles == nil {
		ps, err := LoadProfiles(defaultProfileDirectory)
		if err != nil {
			return nil, err
		}
		loadedProfiles = ps
	}

	for _, p := range loadedProfiles {
		if p.Name == name {
			return p, nil
		}
	}
	return nil, nil
}

func (ps Profiles) GetProfileByPath(bpath string) (*Profile, error) {
	if loadedProfiles == nil {
		ps, err := LoadProfiles(defaultProfileDirectory)
		if err != nil {
			return nil, err
		}
		loadedProfiles = ps
	}

	for _, p := range loadedProfiles {
		if p.Path == bpath {
			return p, nil
		}
		for _, pp := range p.Paths {
			if pp == bpath {
				return p, nil
			}
		}
	}
	return nil, nil
}

func LoadProfiles(dir string) (Profiles, error) {
	fs, err := ioutil.ReadDir(dir)
	if err != nil {
		return nil, err
	}
	ps := []*Profile{}
	for _, f := range fs {
		if !f.IsDir() {
			name := path.Join(dir, f.Name())
			if strings.Contains(f.Name(), ".json") {

				p, err := loadProfileFile(name)
				if err != nil {
					return nil, fmt.Errorf("error loading '%s': %v", f.Name(), err)
				}
				ps = append(ps, p)
			}
		}
	}

	loadedProfiles = ps
	return ps, nil
}

func loadProfileFile(file string) (*Profile, error) {
	bs, err := ioutil.ReadFile(file)
	if err != nil {
		return nil, err
	}
	p := new(Profile)
	if err := json.Unmarshal(bs, p); err != nil {
		return nil, err
	}
	if p.Name == "" {
		p.Name = path.Base(p.Path)
	}
	p.ProfilePath = file
	return p, nil
}
