// Copyright 2021 The TCell Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use file except in compliance with the License.
// You may obtain a copy of the license at
//
//    http://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.

//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris zos

package tcell

import (
	
	
	
	
	
	
	
	

	
	
)

// devTty is an implementation of the Tty API based upon /dev/tty.
type devTty struct {
	fd    int
	f     *os.File
	of    *os.File // the first open of /dev/tty
	saved *term.State
	sig   chan os.Signal
	cb    func()
	stopQ chan struct{}
	dev   string
	wg    sync.WaitGroup
	l     sync.Mutex
}

func ( *devTty) ( []byte) (int, error) {
	return .f.Read()
}

func ( *devTty) ( []byte) (int, error) {
	return .f.Write()
}

func ( *devTty) () error {
	return .f.Close()
}

func ( *devTty) () error {
	.l.Lock()
	defer .l.Unlock()

	// We open another copy of /dev/tty.  This is a workaround for unusual behavior
	// observed in macOS, apparently caused when a subshell (for example) closes our
	// own tty device (when it exits for example).  Getting a fresh new one seems to
	// resolve the problem.  (We believe this is a bug in the macOS tty driver that
	// fails to account for dup() references to the same file before applying close()
	// related behaviors to the tty.)  We're also holding the original copy we opened
	// since closing that might have deleterious effects as well.  The upshot is that
	// we will have up to two separate file handles open on /dev/tty.  (Note that when
	// using stdin/stdout instead of /dev/tty this problem is not observed.)
	var  error
	if .f,  = os.OpenFile(.dev, os.O_RDWR, 0);  != nil {
		return 
	}

	if !term.IsTerminal(.fd) {
		return errors.New("device is not a terminal")
	}

	_ = .f.SetReadDeadline(time.Time{})
	,  := term.MakeRaw(.fd) // also sets vMin and vTime
	if  != nil {
		return 
	}
	.saved = 

	.stopQ = make(chan struct{})
	.wg.Add(1)
	go func( chan struct{}) {
		defer .wg.Done()
		for {
			select {
			case <-.sig:
				.l.Lock()
				 := .cb
				.l.Unlock()
				if  != nil {
					()
				}
			case <-:
				return
			}
		}
	}(.stopQ)

	signal.Notify(.sig, syscall.SIGWINCH)
	return nil
}

func ( *devTty) () error {
	_ = .f.SetReadDeadline(time.Now())
	if  := tcSetBufParams(.fd, 0, 0);  != nil {
		return 
	}
	return nil
}

func ( *devTty) () error {
	.l.Lock()
	if  := term.Restore(.fd, .saved);  != nil {
		.l.Unlock()
		return 
	}
	_ = .f.SetReadDeadline(time.Now())

	signal.Stop(.sig)
	close(.stopQ)
	.l.Unlock()

	.wg.Wait()

	// close our tty device -- we'll get another one if we Start again later.
	_ = .f.Close()

	return nil
}

func ( *devTty) () (WindowSize, error) {
	 := WindowSize{}
	,  := unix.IoctlGetWinsize(.fd, unix.TIOCGWINSZ)
	if  != nil {
		return , 
	}
	 := int(.Col)
	 := int(.Row)
	if  == 0 {
		, _ = strconv.Atoi(os.Getenv("COLUMNS"))
	}
	if  == 0 {
		 = 80 // default
	}
	if  == 0 {
		, _ = strconv.Atoi(os.Getenv("LINES"))
	}
	if  == 0 {
		 = 25 // default
	}
	.Width = 
	.Height = 
	.PixelWidth = int(.Xpixel)
	.PixelHeight = int(.Ypixel)
	return , nil
}

func ( *devTty) ( func()) {
	.l.Lock()
	.cb = 
	.l.Unlock()
}

// NewDevTty opens a /dev/tty based Tty.
func () (Tty, error) {
	return NewDevTtyFromDev("/dev/tty")
}

// NewDevTtyFromDev opens a tty device given a path.  This can be useful to bind to other nodes.
func ( string) (Tty, error) {
	 := &devTty{
		dev: ,
		sig: make(chan os.Signal),
	}
	var  error
	if .of,  = os.OpenFile(, os.O_RDWR, 0);  != nil {
		return nil, 
	}
	.fd = int(.of.Fd())
	if !term.IsTerminal(.fd) {
		_ = .f.Close()
		return nil, errors.New("not a terminal")
	}
	if .saved,  = term.GetState(.fd);  != nil {
		_ = .f.Close()
		return nil, fmt.Errorf("failed to get state: %w", )
	}
	return , nil
}