require 'rubygems'
require 'parseexcel'

class Excel < Openoffice

  def initialize(filename)
    if filename[-4..-1] != ".xls"
      warn "are you sure, this is an excel file?"
    end
    @filename = filename
    @workbook = Spreadsheet::ParseExcel.parse(filename)
    @default_sheet = nil
    @first_row = @last_row = @first_column = @last_column = nil
  end

  def sheets
    #if DateTime.now < Date.new(2007,6,10)
    #  return  ["Tabelle1", "Name of Sheet 2", "Sheet3"]
    #else
      result = []
      0.upto(@workbook.sheet_count - 1) do |i|
        # TODO: is there a better way to do conversion?
        result << Iconv.new('utf-8','unicode').iconv(
                      @workbook.worksheet(i).name
                           )
      end
      return result
    #end
  end

  # sets the working sheet (1,2,3,..)
  #--
  # im Excel-Bereich muesste man wahrscheinlich intern mit Nummern arbeiten
  # von aussen arbeite ich mit (1,2,3... intern wird Index 0,1,2,...
  # verwendet.
  def default_sheet=(n)
    if DateTime.now < Date.new(2007,7,19)
      unless n.kind_of?(Fixnum)
        fail ArgumentError.new("Number expected")
      end
      @default_sheet = n-1
    else
      # parseexcel supports now the name of a sheet
      @default_sheet = n
    end
    @first_row = @last_row = @first_column = @last_column = nil
    @cells_read = false
  end

  # returns the content of a cell. The upper left corner is (1,1) or ('A',1)
  def cell(row,col)
    row,col = normalize(row,col)
    worksheet = @workbook.worksheet(sheet_no(@default_sheet))
    skip = 0
    line = 1
    worksheet.each(skip) { |row_par|
      if line == row
        if row_par == nil
          return nil
        end
        cell = row_par.at(col-1)
        return nil unless cell
        case cell.type
          when :numeric then return cell.to_f
          when :text then return cell.to_s('utf-8')
          when :date then return cell.date
        else  
          return cell.to_s('utf-8')
        end
      end
      line += 1
    }
  end

  # returns the type of a cell: "float", "string", "date"
  def celltype(row,col)
    row,col = normalize(row,col)
    default_sheet_check
    worksheet = @workbook.worksheet(sheet_no(@default_sheet))
    skip = 0
    line = 1
    worksheet.each(skip) { |row_par|
      if line == row
        cell = row_par.at(col-1)
        return nil unless cell
        case cell.type
        when :numeric then return :float
        when :text then return :string
        when :date then return :date
        else  return cell.type.to_sym
        end
      end
      line += 1
    }
  end

  # return this row a an array off cells
  def row(rownumber)
    default_sheet_check
    worksheet = @workbook.worksheet(sheet_no(@default_sheet))
    therow = worksheet.row(rownumber-1) 
    result = []
    therow.each {|cell|
      case cell.type
         when :numeric then result << cell.to_i
         when :text then result << cell.to_s('utf-8')
         when :date then result << cell.date
      else
        #p cell.type
        return result << cell.to_s('utf-8')
      end

      #result << cell.value
    }
    return result
  end

  # returns the first non empty column
  def first_column
    return @first_column if @first_column
    fr, lr, fc, lc = get_firsts_lasts
    fc
  end

  # returns the last non empty column
  def last_column
    return @last_column if @last_column
    fr, lr, fc, lc = get_firsts_lasts
    lc
  end

  # returns the first non empty row
  def first_row
    return @first_row if @first_row
    fr, lr, fc, lc = get_firsts_lasts
    fr
  end

  # returns the last non empty row
  def last_row
    return @last_row if @last_row
    fr, lr, fc, lc = get_firsts_lasts
    lr
  end

  # true if a cell is empty
  def empty?(row, col)
    row,col = normalize(row,col)
    return true if row < first_row || row > last_row || col < first_column || col > last_column
    return true unless cell(row, col)
    return true if celltype(row, col) == "string" && cell(row, col) == ""
    false
  end

private

  # check if default_sheet was set
  def default_sheet_check
    raise ArgumentError, "Error: default_sheet not set" if @default_sheet == nil
  end

  # determine the first and last boundaries
  def get_firsts_lasts
    fr = fc = 999_999
    lr = lc = -999_999  
    worksheet = @workbook.worksheet(sheet_no(@default_sheet))
    skip = 0
    line = 1
    worksheet.each(skip) { |row_par|
      if row_par
        row_par.each_with_index {|cell,i|
          # nicht beruecksichtigen, wenn nil und vorher noch nichts war
          # p cell
          if !cell 
            # nix
          else
            fc = [fc, i+1].min
            lc = [lc, i+1].max
            fr = [fr, line].min
            lr = [lr, line].max
          end
        }
      else
        #???
      end
      line += 1
    }
    @first_row = fr
    @last_row = lr
    @first_column = fc
    @last_column = lc
    return fr, lr, fc, lc
  end


  # converts name of a sheet to index (0,1,2,..)
  def sheet_no(name)
    return name-1 if name.kind_of?(Fixnum)
    0.upto(@workbook.sheet_count - 1) do |i|
      # TODO: is there a better way to do conversion?
      return i if name == Iconv.new('utf-8','unicode').iconv(
                    @workbook.worksheet(i).name
                         )
    end
    raise StandardError, "sheet '#{name}' not found"
  end
end