### This file is part of the 'foreign' package for R.

# Enhancements Copyright (c) 2004-2018 R Development Core Team
#              Copyright (c) 2004      Stephen Eglen

#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  A copy of the GNU General Public License is available at
#  http://www.r-project.org/Licenses/

## Read in a file in Octave text data format.

read.octave <-
function(file)
{
    ## Read in a file in Octave text data format (as created by "save
    ## -ascii" in Octave 2.x) and return a list of the objects
    ## successfully read, along with information on read failures.

    ## E.g., create two variables in Octave

    ## octave> ident_mat = eye(3);
    ## octave> twopi = 2 * pi;
    ## octave> save -ascii 'octfile.dat'

    ## then load this file into R:
    ## > o <- read.octave("octfile.dat")
    ## > o
    ## $twopi
    ## [1] 6.283185
    ## $ident.mat
    ##      [,1] [,2] [,3]
    ## [1,]    1    0    0
    ## [2,]    0    1    0
    ## [3,]    0    0    1

    skip_lines_to_next_item <- function(con) {
        ## Read in data from the connection until we hit the next
        ## variable (assuming "# name" is the first line for a new
        ## variable).  We need to also handle the special case when this
        ## gets us to the end of the connection.
        looking <- TRUE
        while(looking) {
            line <- readLines(con, n = 1L)
            if(length(grep("^# name: ", line)) == 1L) {
                ## We have reached the next variable.
                pushBack(line, con)
                looking <- FALSE
            } else if(length(line) == 0L) {
                ## We have reached the end of file.
                looking <- FALSE
            }
        }
    }

    read_octave_matrix <- function(con) {
        ## Helper function: read in a real matrix/array.
        ## Format of the header varies depending on whether matrix is 2d
        ## or higher.  So need to do a check first.
        line <- readLines(con, 1L)
        if(regexpr("^# rows:", line) > 0L) {
            ## Dealing with a 2d matrix; this header is of the form:
            ## # type: matrix
            ## # rows: 15
            ## # columns: 15
            ## followed by 15 rows of data ...
            nr <- as.integer(gsub("# rows: ", "", line))
            nc <- as.integer(gsub("# columns: ", "", readLines(con, 1L)))
            data <- scan(con, nlines = nr, quiet = TRUE)
            matrix(data, nrow = nr, ncol = nc, byrow = TRUE)
        }
        else {
            ## Assume we have N-d array; this has the format:
            ## # type: matrix
            ## # ndims: 3
            ##  15 15 6
            ## followed by the data, one element per row.
            ## After reading in ndims (3 here), we read in the size of
            ## each dimension (15 x 15 x 6) and then read in the
            ## corresponding number of elements.
##            ndims <- as.integer(gsub("# ndims: ", "", line))
            dims <- scan(con, nlines = 1L, quiet = TRUE)
            data <- scan(con, n = prod(dims), quiet = TRUE)
            array(data, dim = dims)
        }
    }

    read_octave_complex_matrix <- function(con) {
        ## Helper function: read in a complex matrix/array.
        ## See read_octave_matrix().
        line <- readLines(con, 1L)
        if(regexpr("^# rows:", line) > 0L) {
            nr <- as.integer(gsub("# rows: ", "", line))
            nc <- as.integer(gsub("# columns: ", "", readLines(con, 1L)))
            data <- readLines(con, n = nr)
            cl <- paste(data, sep = "", collapse = "")
            c1 <- gsub("\\(", "", cl)
            c1 <- gsub("\\)", "", c1)
            c1 <- gsub(",", " ", c1)
            s <- unlist(strsplit(c1, " "))
            nums <- as.numeric(s[-1L])   # Remove initial space.
            reals <- nums[seq.int(from = 1L, by = 2L, length.out = length(nums)/2)]
            imags <- nums[seq.int(from = 2L, by = 2L, length.out = length(nums)/2)]
            matrix(data = complex(real = reals, imaginary = imags),
                   nrow = nr, ncol = nc, byrow = TRUE)
        }
        else {
##            ndims <- as.integer(gsub("# ndims: ", "", line))
            dims <- scan(con, nlines = 1L, quiet = TRUE)
            data <- readLines(con, n = prod(dims))
            data <- gsub("\\(", "", data)
            data <- gsub("\\)", "", data)
            nums <- strsplit(data, ",")
            # note could replace with lengths if bumping R-version dependancy
            stopifnot(lengths(nums) == 2L)
            array(complex(real = as.numeric(vapply(nums, "[", "", 1L)),
                          imaginary = as.numeric(vapply(nums, "[", "",2L))),
                  dim = dims)
        }
    }

    read_octave_string_array <- function(con) {
        ## Helper function: read in a string array.
        elements <- as.numeric(gsub("# elements: ", "",
                                    readLines(con, 1L)))
        d <- readLines(con, n = 2L * elements)
        ## Remove the odd-numbered lines, they just store "length".
        d[seq.int(from = 2L, by = 2L, length.out = length(d)/2L)]
    }

    read_octave_scalar <- function(con) {
        ## Helper function: read in a scalar.
        as.numeric(scan(con, nlines = 1L, quiet = TRUE))
    }

    read_octave_complex_scalar <- function(con) {
        ## Helper function: read in a complex scalar.
        d <- readLines(con, n = 1L)
        ## Remove parens then split.
        str <- gsub("\\(", "", d)
        str <- gsub("\\)", "", str)
        nums <- as.numeric(unlist(strsplit(str, ",")))
        stopifnot(length(nums) == 2L)
        complex(real = nums[1L], imaginary = nums[2L])
    }


    read_octave_range <- function(con) {
        ## Helper function: read in a range.
        d <- readLines(con, n = 1L) # Skip over "# base, limit, increment".
        d <- as.numeric(scan(con, nlines = 1L, quiet = TRUE))
        stopifnot(length(d) == 3L)
        seq.int(from = d[1L], to = d[2L], by = d[3L])
    }

    read_octave_unknown <- function(con, type) {
        ## Skip over unknown Octave types.
        ## If we do not recognize the type of Octave variable, give a
        ## warning, and try reading until the next variable.
        ## This only works for unknown atomic types, so let us hope we
        ## have code for all recursive ones ...
        warning(gettextf("cannot handle unknown type %s", sQuote(type)),
                domain = NA)
        skip_lines_to_next_item(con)
        NULL
    }

    read_octave_list <- function(con) {
        ## Helper function: read in a list.
        ## Note that lists are deprecated now in favor of cells.
        n <- as.numeric(gsub("# length: ", "", readLines(con, 1L)))
        out <- vector("list", n)
        for(i in seq_len(n)) {
            ## Skip over "# name: _val" lines.
            readLines(con, 1L)
            out[[i]] <- read_item(con)
        }
        out
    }

    read_octave_cell <- function(con) {
        ## Helper function: read in a cell.
        nr <- as.numeric(gsub("# rows: ", "", readLines(con, 1L)))
        nc <- as.numeric(gsub("# columns: ", "", readLines(con, 1L)))
        out <- vector("list", nr * nc)
        dim(out) <- c(nr, nc)
        for(j in seq_len(nc)) {
            for(i in seq_len(nr)) {
                ## Skip over "# name: <cell-element>" lines.
                readLines(con, 1L)
                ## Get the next cell element.
                out[[i, j]] <- read_item(con)
            }
            ## Argh.  There seem to be empty lines after each column of
            ## cell elements?  Let us not rely on this, and instead read
            ## on to the next item.
            skip_lines_to_next_item(con)
        }
        out
    }

    read_octave_struct <- function(con) {
        ## Helper function: read in a struct.
        n <- as.numeric(gsub("# length: ", "", readLines(con, 1L)))
        out <- vector("list", n)
        for(i in seq_len(n)) {
            ## Skip over "# name: _val" lines.
            name <- gsub("# name: ", "", readLines(con, 1L))
            out[[i]] <- read_item(con)
            names(out)[i] <- name
        }
        out
    }

    read_octave_bool <- function(con) {
        ## Helper function: read in a bool.
        as.logical(scan(con, nlines = 1L, quiet = TRUE))
    }

    read_octave_bool_matrix <- function(con) {
        ## Helper function: read in a bool matrix.
        nr <- as.integer(gsub("# rows: ", "", readLines(con, 1L)))
        nc <- as.integer(gsub("# columns: ", "", readLines(con, 1L)))
        data <- scan(con, nlines = nr, quiet = TRUE)
        matrix(as.logical(data), nrow = nr, ncol = nc, byrow = TRUE)
    }

    read_item <- function(con) {
        ## Assume that the name has already been read.
        type <- gsub("# type: ", "", readLines(con, 1L))
        switch(type,
               "matrix" = read_octave_matrix(con),
               "scalar" = read_octave_scalar(con),
               "string" = read_octave_string_array(con),
               "string array" = read_octave_string_array(con),
               "range" = read_octave_range(con),
               "complex matrix" = read_octave_complex_matrix(con),
               "complex scalar" = read_octave_complex_scalar(con),
               "list" = read_octave_list(con),
               "cell" = read_octave_cell(con),
               "struct" = read_octave_struct(con),
               "bool" = read_octave_bool(con),
               "bool matrix" = read_octave_bool_matrix(con),
               read_octave_unknown(con, type))
    }

    zz <- file(file, "r")
    on.exit(close(zz))

    readLines(zz, n = 1L)                # Skip over the header line.

    ## Build up a return list of items -- separately store the return
    ## values and the names.
    items <- list()
    names <- character()
    reading <- TRUE
    while(reading) {
        line <- readLines(zz, 1L, ok = TRUE)
        if(length(line) == 0L) {
            reading <- FALSE
        }
        else {
            items <- c(items, list(read_item(zz)))
            names <- c(names, gsub("# name: ", "", line))
        }
    }

    names(items) <- names
    items
}
