import React from 'react';
import {
  HashRouter as Router,
  Switch,
  Route,
  useParams
} from "react-router-dom";

import Random from './rand';
import './App.css';

type Config = {
  sleepTimeSeconds: number;
  showUI: boolean;

}

let CONFIG: Config;
if (process.env.NODE_ENV === 'production') {
  CONFIG = {
    sleepTimeSeconds: 60,
    showUI: false,
  }
} else {
  CONFIG = {
    sleepTimeSeconds: 10,
    showUI: true,
  }
}

const IMAGE_CHUNK = 256;
const roundup = (v: number, mod: number) => {
  let r = v % mod;
  if (r === 0) {
    return v;
  } else {
    return v + (mod - r);
  }
};

/**
 * Shuffles array in place. ES6 version
 * From https://stackoverflow.com/questions/6274339/how-can-i-shuffle-an-array
 * @param {Array} a items An array containing the items.
 */
function shuffle<T>(a: Array<T>) {
  let rng = new Random(new Date().getDate());
    for (let i = a.length - 1; i > 0; i--) {
        const j = Math.floor(rng.nextFloat() * (i + 1));
        [a[i], a[j]] = [a[j], a[i]];
    }
    return a;
}

class Slide {
  // One or two items.  For example if display is landscape we'll try to fit
  // two portrait images and only one landscape. 
  items: Array<MediaItem>;
  nextSlide?: Slide;
  prevSlide?: Slide;
  constructor(items: Array<MediaItem>) {
    this.items = items;
  }
  prefetchImages() {
    console.log(`prefetchImages, I have ${this.imageUrls.length} images`);
    this.imageUrls.map(url => new Image().src = url);
  }
  get imageUrls(): Array<string> {
    let w = window.innerWidth * window.devicePixelRatio;
    let h = window.innerHeight * window.devicePixelRatio;
    let ratio = w/h;
    if (ratio > 1) {
      // Landscape image
      w = roundup(w, IMAGE_CHUNK);
      h = Math.round(w/ratio);
    } else {
      // Portrait image
      h = roundup(h, IMAGE_CHUNK);
      w = Math.round(h/ratio);
    }
    //console.log(`Window size ${window.innerWidth}x${window.innerHeight} with a devicePixelRatio of ${window.devicePixelRatio} for a total size of ${w}x${h}`);
    return this.items.map(img => `/api/image/${img.id}?w=${w}&h=${h}`);
  }
  render() {
    let urls = this.imageUrls;
    let frac = 100 / urls.length;
    let imgs = urls.map(url => {
      // TODO(wathiede): make this landscape/portrait aware.
      let style: React.CSSProperties = {
        height: '100%',
        width: frac + '%',
        backgroundColor: 'black',
        backgroundImage: `url(${url})`,
        backgroundRepeat: 'no-repeat',
        backgroundPosition: 'center center',
        backgroundSize: 'cover',
        float: 'left',
      };
      return <div key={url} style={style}></div>;
    });
    // TODO(wathiede): make sure the style  handles multiple items.
    return <div style={{
        height: '100%',
        width: '100%',
    }}>{imgs}</div>;
  }
};

function makePairs<T>(items: Array<T>) {
  const half = Math.floor(items.length/2);
  console.log(`items ${items.length} half ${half}`)
  let pairs = [];
  for (let i = 0; i < half; i++) {
    pairs.push([items[2*i], items[2*i+1]]);
  }
  return pairs;
}

type MediaMetadata = {
  width: number;
  height: number;
};
type MediaItem = {
  id: string;
  mediaMetadata: MediaMetadata;
  filename: string;
};
type AlbumProps = {
  album: string;
  showUI: boolean;
  sleepTimeSeconds: number;
};
type AlbumState = {
  error: any;
  mediaItems: Array<MediaItem> | null;
  curSlide?: Slide,
  showUI: boolean;
  timerID: any | null;
};
class Album extends React.Component<AlbumProps, AlbumState> {
  state: AlbumState = {
    error: null,
    mediaItems: null,
    showUI: this.props.showUI,
    timerID: null,
  };
  componentDidMount() {
    this.loadAlbum()
  }
  loadAlbum() {
    let {album} = this.props;
    fetch(process.env.PUBLIC_URL + `/api/album/${album}`)
      .then(res => res.json())
      .then(
        (mediaItems: Array<MediaItem>) => {
          let w = window.innerWidth * window.devicePixelRatio;
          let h = window.innerHeight * window.devicePixelRatio;
          let ratio = w/h;
          let landscapes = mediaItems.filter((mi) => {
            let md = mi.mediaMetadata;
            let ratio = md.width/md.height;
            return ratio > 1;
          });

          let portraits = mediaItems.filter((mi) => {
            let md = mi.mediaMetadata;
            let ratio = md.width/md.height;
            return ratio <= 1;
          });

          console.log(`${landscapes.length} landscape photos`);
          console.log(`${portraits.length} portraits photos`);
          let slides: Array<Slide>;
          if (ratio > 1) {
            console.log('display in landscape mode');
            slides = landscapes.map((p)=>{
              return new Slide([p]);
            });
            let pairs = makePairs(shuffle(portraits));
            slides = slides.concat(pairs.map((p, i) => new Slide(p)));
          } else {
            console.log('display in portrait mode');
            slides = portraits.map((p)=>{
              return new Slide([p]);
            });
            // TODO(wathiede): fix Slide::render before adding landscapes
            // to slides here.
          }
          slides = shuffle(slides);
          console.log(`${slides.length} slides`);
          let numSlides = slides.length;
          slides.forEach((p, idx)=>{
            let nextIdx = (idx+1)%numSlides;
            let prevIdx = (numSlides+idx-1)%numSlides;
            p.nextSlide = slides[nextIdx];
            p.prevSlide = slides[prevIdx];
          })

          this.setState({curSlide: slides[0]});
          let {sleepTimeSeconds} = this.props;
          let timerID = setInterval(()=>{
            let {curSlide} = this.state;
            this.setState({curSlide: curSlide?.nextSlide})
            console.log('timer fired');
          }, sleepTimeSeconds*1000);
          this.setState({timerID});
        },
        (error) => this.setState({error}),
      );
  }
  componentWillUnmount() {
    let {timerID} = this.state;
    clearInterval(timerID);
  }
  nextPhoto() {
  }
  render() {
    // TODO(wathiede): fade transition.
    let {curSlide, error, showUI} = this.state;
    if (error !== null) {
      return <h2>Error: {JSON.stringify(error)}</h2>;
    } else if (curSlide) {
      let nextSlide = curSlide?.nextSlide;
      let prevSlide = curSlide?.prevSlide;
      let prefetchStyle: React.CSSProperties = {
        backgroundColor: 'rgba(127, 127, 127, 0.5)',
        backgroundPosition: 'center center',
        bottom: 0,
        height: '25%',
        position: 'absolute',
        width: '25%',
      };
      let leftPrefetchStyle: React.CSSProperties = {
        left: 0,
        ...prefetchStyle
      };
      let rightPrefetchStyle: React.CSSProperties = {
        right: 0,
        ...prefetchStyle
      };
      let ui;
      if (showUI) {
        ui = <div id="ui">
          <div
          style={leftPrefetchStyle}
          onClick={(e)=>{
            e.stopPropagation();
            this.setState({curSlide: curSlide?.prevSlide})
          }}>{ prevSlide?.render() }</div>
          {/* TODO(wathiede): make this work with multiple items. */}
          <div className="meta">{curSlide?.items.map(i=>i.filename).join(' | ')}</div>
          <div
          style={rightPrefetchStyle}
          onClick={(e)=>{
            e.stopPropagation();
            this.setState({curSlide: curSlide?.nextSlide})
          }}>{ nextSlide?.render() }</div>
        </div>;
      }
      nextSlide?.prefetchImages();
      return <div id="slide" onClick={(e)=>{
        e.stopPropagation();
        this.setState({showUI: !showUI})
      }}>
        { curSlide?.render() }
        { ui }
      </div>;
    } else {
      return <h2>Loading...</h2>;
    }
  }
}

type AlbumIndexProps = {
};
type AlbumIndexState = {
  error: any | null,
  albums: Array<any> | null,
};
class AlbumIndex extends React.Component<AlbumIndexProps, AlbumIndexState> {
  state: AlbumIndexState = {
    error: null,
    albums: null,
  }
  componentDidMount() {
    fetch(process.env.PUBLIC_URL + "/api/albums")
      .then(res => res.json())
      .then(
        (result) =>  this.setState({albums: result}),
        (error) => this.setState({error}),
      );
  }
  render() {
    let {error, albums} = this.state;
    if (error !== null) {
      return <h2>Error: {JSON.stringify(error)}</h2>;
    } else if (albums !== null) {
      return albums.map((a) => {
        let img = <img src="https://via.placeholder.com/256x128" className="mr-3" alt="unset"/>;
        if (a.coverPhotoMediaItemId !== undefined) {
          img = <img src={ `/api/image/${a.coverPhotoMediaItemId}?w=256&h=256` } className="mr-3" alt={ a.title }/>
        }

        let figure = <figure key={ a.id } className="figure">
          {img}
          <figcaption className="figure-caption">{ a.title || "No title" } - { a.mediaItemsCount || 0 } photos </figcaption>
        </figure>;
        return <a key={ a.id } href={ '#' + a.id }>
            { figure }
          </a>
      });
    } else {
      return <h2>Loading...</h2>;
    }
  }
}

type AlbumRouteProps = { sleepTimeSeconds: number, showUI: boolean };
const AlbumRoute = ({sleepTimeSeconds, showUI}: AlbumRouteProps) => {
  // We can use the `useParams` hook here to access
  // the dynamic pieces of the URL.
  let { albumId } = useParams();
  albumId = albumId || '';
  return <Album album={albumId} showUI={showUI} sleepTimeSeconds={sleepTimeSeconds} />;
}

const App = () => {
  let {showUI, sleepTimeSeconds} = CONFIG;
  return <Router>
      <Switch>
        <Route exact path="/">
          <div className="container">
            <AlbumIndex />
          </div>
        </Route>
        <Route exact path="/:albumId">
          <AlbumRoute showUI={showUI} sleepTimeSeconds={sleepTimeSeconds} />
        </Route>
      </Switch>
    </Router>
}

export default App;
