package main

import (
	"bufio"
	"flag"
	"fmt"
	"log"
	"os"
	"regexp"
	"strconv"
	"strings"
)

func printUsage() {
	fmt.Fprintf(os.Stderr, "Usage: %s [options] <range specification> [file]\n", os.Args[0])
	fmt.Fprintln(os.Stderr, "Options:")
	flag.PrintDefaults()
	fmt.Fprintln(os.Stderr, "\nRange specification examples:")
	fmt.Fprintln(os.Stderr, "10,20      Reads lines 10 to 20 from the input")
	fmt.Fprintln(os.Stderr, "15:+5      Reads 5 lines starting from line 15")
	fmt.Fprintln(os.Stderr, "3          Reads from line 3 to the end of the file")
	fmt.Fprintln(os.Stderr, "+2         Reads the first 2 lines")
	fmt.Fprintln(os.Stderr, "Please ensure you input valid range specifications and file paths.")
	os.Exit(1)
}

func parseArgs(args []string) (int, int, string, error) {
	var filename string
	var start, length, end int
	found := false

	// Concatenate all arguments to a single string for easier parsing
	joinedArgs := strings.Join(args, " ")

	// Regular expression to match ranges and numbers
	rangeRegex := regexp.MustCompile(`((\d+)?([ :,;-]))(\+)?(\d+)`)
	range2Regex := regexp.MustCompile(`((\d+)([ :,;-]))`)
	range3Regex := regexp.MustCompile(`(\+)?(\d+)`)

	matches := rangeRegex.FindStringSubmatch(joinedArgs)

	if matches != nil {
		//check if start was defined
		if matches[2] != "" {
			start, _ = strconv.Atoi(matches[2]) // Convert start line to integer
		} else {
			start = 1
		}
		if matches[4] == "+" { // Check if it's a relative length
			length, _ = strconv.Atoi(matches[5]) // Convert length to integer
			end = start + length - 1
		} else {
			end, _ = strconv.Atoi(matches[5]) // Convert end line to integer
		}
		// Remove the matched part from the arguments
		joinedArgs = strings.Replace(joinedArgs, matches[0], "", 1)
		found = true
	} else {
		matches = range2Regex.FindStringSubmatch(joinedArgs)
		if matches != nil {
			start, _ = strconv.Atoi(matches[2]) // Convert start line to integer
			end = -1
			// Remove the matched part from the arguments
			joinedArgs = strings.Replace(joinedArgs, matches[0], "", 1)
			found = true
		} else {
			matches = range3Regex.FindStringSubmatch(joinedArgs)
			if matches != nil {
				if matches[1] == "+" { // Check if it's a relative length
					length, _ := strconv.Atoi(matches[2]) // Convert length to integer
					start = 1
					end = start + length - 1
				} else { // Otherwise convert it to an absolute length
					start, _ = strconv.Atoi(matches[2]) // Convert start line to integer
					end = -1
					// Remove the matched part from the arguments
				}
				joinedArgs = strings.Replace(joinedArgs, matches[0], "", 1)
				found = true
			} else {
				found = false
				printUsage()
			}
		}
	}

	// Clean up and identify the filename, if present
	joinedArgs = strings.TrimSpace(joinedArgs)
	if joinedArgs != "" && !found {
		// If we didn't find numbers, interpret the remaining as filename
		filename = joinedArgs
	} else if joinedArgs != "" {
		// Otherwise, interpret any non-empty remaining part as filename
		filename = joinedArgs
	}

	if !found {
		return 0, 0, "", fmt.Errorf("no valid range or line number found")
	}

	return start, end, filename, nil
}

func main() {
	flag.Parse()
	args := flag.Args()

	if len(args) < 1 {
		printUsage()
		os.Exit(1)
	}

	startIndex, endIndex, filename, err := parseArgs(args)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error parsing arguments: %v\n", err)
		os.Exit(1)
	}

	// Detemine the input source (file or stdin)
	inputSource := os.Stdin
	if filename != "" {
		inputSource, err = os.Open(filename)
		if err != nil {
			log.Fatalf("Failed to open file: %v\n", err)
		}
		defer inputSource.Close()
	}

	scanner := bufio.NewScanner(inputSource)
	//print all lines from line buner start to linenumber end unless linenumber end is -1
	for i := 1; scanner.Scan(); i++ {
		if startIndex <= i && (i <= endIndex || endIndex == -1) {
			fmt.Println(scanner.Text())
		}
	}
	if err := scanner.Err(); err != nil {
		log.Fatal(err)
	}

}
