(* Copyright 2005-2007 Berke DURAK, INRIA Rocquencourt.

This file is part of Dose2.

Dose2 is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Dose2 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 Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>. *)

(** A progress indicator module *)

class type progress_indicator =
  object
    method set_label : string -> unit
    method set_decimation : int -> unit
    method display : ?force:bool -> int * int -> unit
    method start : unit
    method finish : unit
    method warn : string -> unit
  end
;;

let dummy =
  object(self)
    method set_label _ = ()
    method set_decimation _ = ()
    method display ?force (_,_) = ()
    method start = ()
    method finish = ()
    method warn msg = ()
  end
;;

class virtual generic_indicator ~decimation ~label:initial_label () =
  let delay = 0.100 in (* 100 ms *)
  let full =        "* 100.0%" in
  (*let unspecified = "        " in*)
  let reserved = String.length full in
  let rotate = "|/-\\" in
  let columns = 75 in
  object(self)
    val mutable last = (-1,-1)
    val mutable stamp = 0.0
    val mutable label = ""
    val mutable count = 0
    val mutable rotation = 0
    val mutable decimation = decimation
    val b = Buffer.create columns

    initializer
      self#set_label initial_label

    method private virtual output : unit
    
    method set_label l =
      let w = Util.limit (columns - reserved) l in
      label <- w^(String.make (columns - reserved - (String.length w)) ' ')

    method set_decimation d =
      decimation <- d;
      count <- 0

    method display ?(force=false) (x,y) =
      Buffer.clear b;
      if decimation > 0 then
        begin
          count <- count + 1;
          if count = decimation then
            count <- 0;
        end;
      if force or ((decimation = 0 or count = 1 or y = 0) (* && (x,y) <> last *)) then
        begin
          let t = Unix.gettimeofday () in
          if force or stamp +. delay < t then
            begin
              stamp <- t;
              last <- (x,y);
              Buffer.add_char b '\r';
              Buffer.add_string b label;
              if y = 0 then
                Printf.bprintf b " %-7d" x
              else
                begin
                  let f = floor (1000.0 *. (float x) /. (float y)) in
                  let f = f /. 10.0 in
                  if f = 100.0 then
                    Buffer.add_string b full
                  else
                    begin
                      rotation <- (1 + rotation) land 3;
                      Printf.bprintf b "%c %%%4.1f" rotate.[rotation] f
                    end
                end;
              self#output
            end
        end

    method start =
      self#display ~force:true (0,0);
      self#output

    method finish =
      self#display ~force:true (100,100);
      Buffer.add_char b '\n';
      self#output

    method warn msg =
      Printf.printf "\n%s\n" msg;
      self#display ~force:true last
  end
;;

class indicator ~decimation ~label ~channel () =
  object(self)
    inherit generic_indicator ~decimation ~label () as super
    method private output =
      Buffer.output_buffer channel b;
      flush channel
  end
;;

class formatter_indicator ~decimation ~label ~fmt () =
  object(self)
    inherit generic_indicator ~decimation ~label () as super
    method private output =
      let x = Buffer.contents b in
      Format.fprintf fmt "%s@?" x;
  end
;;
